//! Rustdoc's HTML rendering module. //! //! This modules contains the bulk of the logic necessary for rendering a //! rustdoc `clean::Crate` instance to a set of static HTML pages. This //! rendering process is largely driven by the `format!` syntax extension to //! perform all I/O into files and streams. //! //! The rendering process is largely driven by the `Context` and `Cache` //! structures. The cache is pre-populated by crawling the crate in question, //! and then it is shared among the various rendering threads. The cache is meant //! to be a fairly large structure not implementing `Clone` (because it's shared //! among threads). The context, however, should be a lightweight structure. This //! is cloned per-thread and contains information about what is currently being //! rendered. //! //! The main entry point to the rendering system is the implementation of //! `FormatRenderer` on `Context`. //! //! In order to speed up rendering (mostly because of markdown rendering), the //! rendering process has been parallelized. This parallelization is only //! exposed through the `crate` method on the context, and then also from the //! fact that the shared cache is stored in TLS (and must be accessed as such). //! //! In addition to rendering the crate itself, this module is also responsible //! for creating the corresponding search index and source file renderings. //! These threads are not parallelized (they haven't been a bottleneck yet), and //! both occur before the crate is rendered. pub(crate) mod search_index; #[cfg(test)] mod tests; mod context; mod ordered_json; mod print_item; pub(crate) mod sidebar; mod sorted_template; mod span_map; mod type_layout; mod write_shared; use std::borrow::Cow; use std::collections::VecDeque; use std::fmt::{self, Display as _, Write}; use std::iter::Peekable; use std::path::PathBuf; use std::{fs, str}; use askama::Template; use itertools::Either; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::attrs::{DeprecatedSince, Deprecation}; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ConstStability, Mutability, RustcVersion, StabilityLevel, StableSince}; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::symbol::{Symbol, sym}; use rustc_span::{BytePos, DUMMY_SP, FileName, RealFileName}; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; use tracing::{debug, info}; pub(crate) use self::context::*; pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources}; pub(crate) use self::write_shared::*; use crate::clean::{self, ItemId, RenderedLink}; use crate::display::{Joined as _, MaybeDisplay as _}; use crate::error::Error; use crate::formats::Impl; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::format::{ Ending, HrefError, PrintWithSpace, href, print_abi_with_space, print_constness_with_space, print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space, write_str, }; use crate::html::markdown::{ HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine, }; use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD; use crate::html::{highlight, sources}; use crate::scrape_examples::{CallData, CallLocation}; use crate::{DOC_RUST_LANG_ORG_VERSION, try_none}; pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display { fmt::from_fn(move |f| { if !v.ends_with('/') && !v.is_empty() { write!(f, "{v}/") } else { f.write_str(v) } }) } /// Specifies whether rendering directly implemented trait items or ones from a certain Deref /// impl. #[derive(Copy, Clone, Debug)] enum AssocItemRender<'a> { All, DerefFor { trait_: &'a clean::Path, type_: &'a clean::Type, deref_mut_: bool }, } impl AssocItemRender<'_> { fn render_mode(&self) -> RenderMode { match self { Self::All => RenderMode::Normal, &Self::DerefFor { deref_mut_, .. } => RenderMode::ForDeref { mut_: deref_mut_ }, } } fn class(&self) -> Option<&'static str> { if let Self::DerefFor { .. } = self { Some("impl-items") } else { None } } } /// For different handling of associated items from the Deref target of a type rather than the type /// itself. #[derive(Copy, Clone, PartialEq)] enum RenderMode { Normal, ForDeref { mut_: bool }, } // Helper structs for rendering items/sidebars and carrying along contextual // information /// Struct representing one entry in the JS search index. These are all emitted /// by hand to a large JS file at the end of cache-creation. #[derive(Debug)] pub(crate) struct IndexItem { pub(crate) ty: ItemType, pub(crate) defid: Option, pub(crate) name: Symbol, pub(crate) module_path: Vec, pub(crate) desc: String, pub(crate) parent: Option, pub(crate) parent_idx: Option, pub(crate) trait_parent: Option, pub(crate) trait_parent_idx: Option, pub(crate) exact_module_path: Option>, pub(crate) impl_id: Option, pub(crate) search_type: Option, pub(crate) aliases: Box<[Symbol]>, pub(crate) deprecation: Option, } /// A type used for the search index. #[derive(Debug, Eq, PartialEq)] struct RenderType { id: Option, generics: Option>, bindings: Option)>>, } impl RenderType { fn size(&self) -> usize { let mut size = 1; if let Some(generics) = &self.generics { size += generics.iter().map(RenderType::size).sum::(); } if let Some(bindings) = &self.bindings { for (_, constraints) in bindings.iter() { size += 1; size += constraints.iter().map(RenderType::size).sum::(); } } size } // Types are rendered as lists of lists, because that's pretty compact. // The contents of the lists are always integers in self-terminating hex // form, handled by `RenderTypeId::write_to_string`, so no commas are // needed to separate the items. fn write_to_string(&self, string: &mut String) { fn write_optional_id(id: Option, string: &mut String) { // 0 is a sentinel, everything else is one-indexed match id { Some(id) => id.write_to_string(string), None => string.push('`'), } } // Either just the type id, or `{type, generics, bindings?}` // where generics is a list of types, // and bindings is a list of `{id, typelist}` pairs. if self.generics.is_some() || self.bindings.is_some() { string.push('{'); write_optional_id(self.id, string); string.push('{'); for generic in self.generics.as_deref().unwrap_or_default() { generic.write_to_string(string); } string.push('}'); if self.bindings.is_some() { string.push('{'); for binding in self.bindings.as_deref().unwrap_or_default() { string.push('{'); binding.0.write_to_string(string); string.push('{'); for constraint in &binding.1[..] { constraint.write_to_string(string); } string.push_str("}}"); } string.push('}'); } string.push('}'); } else { write_optional_id(self.id, string); } } fn read_from_bytes(string: &[u8]) -> (RenderType, usize) { let mut i = 0; if string[i] == b'{' { i += 1; let (id, offset) = RenderTypeId::read_from_bytes(&string[i..]); i += offset; let generics = if string[i] == b'{' { i += 1; let mut generics = Vec::new(); while string[i] != b'}' { let (ty, offset) = RenderType::read_from_bytes(&string[i..]); i += offset; generics.push(ty); } assert!(string[i] == b'}'); i += 1; Some(generics) } else { None }; let bindings = if string[i] == b'{' { i += 1; let mut bindings = Vec::new(); while string[i] == b'{' { i += 1; let (binding, boffset) = RenderTypeId::read_from_bytes(&string[i..]); i += boffset; let mut bconstraints = Vec::new(); assert!(string[i] == b'{'); i += 1; while string[i] != b'}' { let (constraint, coffset) = RenderType::read_from_bytes(&string[i..]); i += coffset; bconstraints.push(constraint); } assert!(string[i] == b'}'); i += 1; bindings.push((binding.unwrap(), bconstraints)); assert!(string[i] == b'}'); i += 1; } assert!(string[i] == b'}'); i += 1; Some(bindings) } else { None }; assert!(string[i] == b'}'); i += 1; (RenderType { id, generics, bindings }, i) } else { let (id, offset) = RenderTypeId::read_from_bytes(string); i += offset; (RenderType { id, generics: None, bindings: None }, i) } } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum RenderTypeId { DefId(DefId), Primitive(clean::PrimitiveType), AssociatedType(Symbol), Index(isize), Mut, } impl RenderTypeId { fn write_to_string(&self, string: &mut String) { let id: i32 = match &self { // 0 is a sentinel, everything else is one-indexed // concrete type RenderTypeId::Index(idx) if *idx >= 0 => (idx + 1isize).try_into().unwrap(), // generic type parameter RenderTypeId::Index(idx) => (*idx).try_into().unwrap(), _ => panic!("must convert render types to indexes before serializing"), }; search_index::encode::write_signed_vlqhex_to_string(id, string); } fn read_from_bytes(string: &[u8]) -> (Option, usize) { let Some((value, offset)) = search_index::encode::read_signed_vlqhex_from_string(string) else { return (None, 0); }; let value = isize::try_from(value).unwrap(); let ty = match value { ..0 => Some(RenderTypeId::Index(value)), 0 => None, 1.. => Some(RenderTypeId::Index(value - 1)), }; (ty, offset) } } /// Full type of functions/methods in the search index. #[derive(Debug, Eq, PartialEq)] pub(crate) struct IndexItemFunctionType { inputs: Vec, output: Vec, where_clause: Vec>, param_names: Vec>, } impl IndexItemFunctionType { fn size(&self) -> usize { self.inputs.iter().map(RenderType::size).sum::() + self.output.iter().map(RenderType::size).sum::() + self .where_clause .iter() .map(|constraints| constraints.iter().map(RenderType::size).sum::()) .sum::() } fn read_from_string_without_param_names(string: &[u8]) -> (IndexItemFunctionType, usize) { let mut i = 0; if string[i] == b'`' { return ( IndexItemFunctionType { inputs: Vec::new(), output: Vec::new(), where_clause: Vec::new(), param_names: Vec::new(), }, 1, ); } assert_eq!(b'{', string[i]); i += 1; fn read_args_from_string(string: &[u8]) -> (Vec, usize) { let mut i = 0; let mut params = Vec::new(); if string[i] == b'{' { // multiple params i += 1; while string[i] != b'}' { let (ty, offset) = RenderType::read_from_bytes(&string[i..]); i += offset; params.push(ty); } i += 1; } else if string[i] != b'}' { let (tyid, offset) = RenderTypeId::read_from_bytes(&string[i..]); params.push(RenderType { id: tyid, generics: None, bindings: None }); i += offset; } (params, i) } let (inputs, offset) = read_args_from_string(&string[i..]); i += offset; let (output, offset) = read_args_from_string(&string[i..]); i += offset; let mut where_clause = Vec::new(); while string[i] != b'}' { let (constraint, offset) = read_args_from_string(&string[i..]); i += offset; where_clause.push(constraint); } assert_eq!(b'}', string[i], "{} {}", String::from_utf8_lossy(&string), i); i += 1; (IndexItemFunctionType { inputs, output, where_clause, param_names: Vec::new() }, i) } fn write_to_string_without_param_names<'a>(&'a self, string: &mut String) { // If we couldn't figure out a type, just write 0, // which is encoded as `` ` `` (see RenderTypeId::write_to_string). let has_missing = self .inputs .iter() .chain(self.output.iter()) .any(|i| i.id.is_none() && i.generics.is_none()); if has_missing { string.push('`'); } else { string.push('{'); match &self.inputs[..] { [one] if one.generics.is_none() && one.bindings.is_none() => { one.write_to_string(string); } _ => { string.push('{'); for item in &self.inputs[..] { item.write_to_string(string); } string.push('}'); } } match &self.output[..] { [] if self.where_clause.is_empty() => {} [one] if one.generics.is_none() && one.bindings.is_none() => { one.write_to_string(string); } _ => { string.push('{'); for item in &self.output[..] { item.write_to_string(string); } string.push('}'); } } for constraint in &self.where_clause { if let [one] = &constraint[..] && one.generics.is_none() && one.bindings.is_none() { one.write_to_string(string); } else { string.push('{'); for item in &constraint[..] { item.write_to_string(string); } string.push('}'); } } string.push('}'); } } } #[derive(Debug, Clone)] pub(crate) struct StylePath { /// The path to the theme pub(crate) path: PathBuf, } impl StylePath { pub(crate) fn basename(&self) -> Result { Ok(try_none!(try_none!(self.path.file_stem(), &self.path).to_str(), &self.path).to_string()) } } #[derive(Debug, Eq, PartialEq, Hash)] struct ItemEntry { url: String, name: String, } impl ItemEntry { fn new(mut url: String, name: String) -> ItemEntry { while url.starts_with('/') { url.remove(0); } ItemEntry { url, name } } } impl ItemEntry { fn print(&self) -> impl fmt::Display { fmt::from_fn(move |f| write!(f, "{}", self.url, Escape(&self.name))) } } impl PartialOrd for ItemEntry { fn partial_cmp(&self, other: &ItemEntry) -> Option<::std::cmp::Ordering> { Some(self.cmp(other)) } } impl Ord for ItemEntry { fn cmp(&self, other: &ItemEntry) -> ::std::cmp::Ordering { self.name.cmp(&other.name) } } #[derive(Debug)] struct AllTypes { structs: FxIndexSet, enums: FxIndexSet, unions: FxIndexSet, primitives: FxIndexSet, traits: FxIndexSet, macros: FxIndexSet, functions: FxIndexSet, type_aliases: FxIndexSet, statics: FxIndexSet, constants: FxIndexSet, attribute_macros: FxIndexSet, derive_macros: FxIndexSet, trait_aliases: FxIndexSet, } impl AllTypes { fn new() -> AllTypes { let new_set = |cap| FxIndexSet::with_capacity_and_hasher(cap, Default::default()); AllTypes { structs: new_set(100), enums: new_set(100), unions: new_set(100), primitives: new_set(26), traits: new_set(100), macros: new_set(100), functions: new_set(100), type_aliases: new_set(100), statics: new_set(100), constants: new_set(100), attribute_macros: new_set(100), derive_macros: new_set(100), trait_aliases: new_set(100), } } fn append(&mut self, item_name: String, item_type: &ItemType) { let mut url: Vec<_> = item_name.split("::").skip(1).collect(); if let Some(name) = url.pop() { let new_url = format!("{}/{item_type}.{name}.html", url.join("/")); url.push(name); let name = url.join("::"); match *item_type { ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)), ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)), ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)), ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)), ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)), ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)), ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)), ItemType::TypeAlias => self.type_aliases.insert(ItemEntry::new(new_url, name)), ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)), ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)), ItemType::ProcAttribute => { self.attribute_macros.insert(ItemEntry::new(new_url, name)) } ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), _ => true, }; } } fn item_sections(&self) -> FxHashSet { let mut sections = FxHashSet::default(); if !self.structs.is_empty() { sections.insert(ItemSection::Structs); } if !self.enums.is_empty() { sections.insert(ItemSection::Enums); } if !self.unions.is_empty() { sections.insert(ItemSection::Unions); } if !self.primitives.is_empty() { sections.insert(ItemSection::PrimitiveTypes); } if !self.traits.is_empty() { sections.insert(ItemSection::Traits); } if !self.macros.is_empty() { sections.insert(ItemSection::Macros); } if !self.functions.is_empty() { sections.insert(ItemSection::Functions); } if !self.type_aliases.is_empty() { sections.insert(ItemSection::TypeAliases); } if !self.statics.is_empty() { sections.insert(ItemSection::Statics); } if !self.constants.is_empty() { sections.insert(ItemSection::Constants); } if !self.attribute_macros.is_empty() { sections.insert(ItemSection::AttributeMacros); } if !self.derive_macros.is_empty() { sections.insert(ItemSection::DeriveMacros); } if !self.trait_aliases.is_empty() { sections.insert(ItemSection::TraitAliases); } sections } fn print(&self) -> impl fmt::Display { fn print_entries(e: &FxIndexSet, kind: ItemSection) -> impl fmt::Display { fmt::from_fn(move |f| { if e.is_empty() { return Ok(()); } let mut e: Vec<&ItemEntry> = e.iter().collect(); e.sort(); write!( f, "

{title}

    ", id = kind.id(), title = kind.name(), )?; for s in e.iter() { write!(f, "
  • {}
  • ", s.print())?; } f.write_str("
") }) } fmt::from_fn(|f| { f.write_str("

List of all items

")?; // Note: print_entries does not escape the title, because we know the current set of titles // doesn't require escaping. print_entries(&self.structs, ItemSection::Structs).fmt(f)?; print_entries(&self.enums, ItemSection::Enums).fmt(f)?; print_entries(&self.unions, ItemSection::Unions).fmt(f)?; print_entries(&self.primitives, ItemSection::PrimitiveTypes).fmt(f)?; print_entries(&self.traits, ItemSection::Traits).fmt(f)?; print_entries(&self.macros, ItemSection::Macros).fmt(f)?; print_entries(&self.attribute_macros, ItemSection::AttributeMacros).fmt(f)?; print_entries(&self.derive_macros, ItemSection::DeriveMacros).fmt(f)?; print_entries(&self.functions, ItemSection::Functions).fmt(f)?; print_entries(&self.type_aliases, ItemSection::TypeAliases).fmt(f)?; print_entries(&self.trait_aliases, ItemSection::TraitAliases).fmt(f)?; print_entries(&self.statics, ItemSection::Statics).fmt(f)?; print_entries(&self.constants, ItemSection::Constants).fmt(f)?; Ok(()) }) } } fn scrape_examples_help(shared: &SharedContext<'_>) -> String { let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned(); content.push_str(&format!( "## More information\n\n\ If you want more information about this feature, please read the [corresponding chapter in \ the Rustdoc book]({DOC_RUST_LANG_ORG_VERSION}/rustdoc/scraped-examples.html)." )); format!( "
\

About scraped examples

\
\
{}
", fmt::from_fn(|f| Markdown { content: &content, links: &[], ids: &mut IdMap::default(), error_codes: shared.codes, edition: shared.edition(), playground: &shared.playground, heading_offset: HeadingOffset::H1, } .write_into(f)) ) } fn document( cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>, heading_offset: HeadingOffset, ) -> impl fmt::Display { if let Some(ref name) = item.name { info!("Documenting {name}"); } fmt::from_fn(move |f| { document_item_info(cx, item, parent).render_into(f)?; if parent.is_none() { write!(f, "{}", document_full_collapsible(item, cx, heading_offset)) } else { write!(f, "{}", document_full(item, cx, heading_offset)) } }) } /// Render md_text as markdown. fn render_markdown( cx: &Context<'_>, md_text: &str, links: Vec, heading_offset: HeadingOffset, ) -> impl fmt::Display { fmt::from_fn(move |f| { f.write_str("
")?; Markdown { content: md_text, links: &links, ids: &mut cx.id_map.borrow_mut(), error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, heading_offset, } .write_into(&mut *f)?; f.write_str("
") }) } /// Writes a documentation block containing only the first paragraph of the documentation. If the /// docs are longer, a "Read more" link is appended to the end. fn document_short( item: &clean::Item, cx: &Context<'_>, link: AssocItemLink<'_>, parent: &clean::Item, show_def_docs: bool, ) -> impl fmt::Display { fmt::from_fn(move |f| { document_item_info(cx, item, Some(parent)).render_into(f)?; if !show_def_docs { return Ok(()); } let s = item.doc_value(); if !s.is_empty() { let (mut summary_html, has_more_content) = MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); let link = if has_more_content { let link = fmt::from_fn(|f| { write!( f, " Read more", assoc_href_attr(item, link, cx).maybe_display() ) }); if let Some(idx) = summary_html.rfind("

") { summary_html.insert_str(idx, &link.to_string()); None } else { Some(link) } } else { None } .maybe_display(); write!(f, "
{summary_html}{link}
")?; } Ok(()) }) } fn document_full_collapsible( item: &clean::Item, cx: &Context<'_>, heading_offset: HeadingOffset, ) -> impl fmt::Display { document_full_inner(item, cx, true, heading_offset) } fn document_full( item: &clean::Item, cx: &Context<'_>, heading_offset: HeadingOffset, ) -> impl fmt::Display { document_full_inner(item, cx, false, heading_offset) } fn document_full_inner( item: &clean::Item, cx: &Context<'_>, is_collapsible: bool, heading_offset: HeadingOffset, ) -> impl fmt::Display { fmt::from_fn(move |f| { if let Some(s) = item.opt_doc_value() { debug!("Doc block: =====\n{s}\n====="); if is_collapsible { write!( f, "
\ \ Expand description\ {}
", render_markdown(cx, &s, item.links(cx), heading_offset) )?; } else { write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?; } } let kind = match &item.kind { clean::ItemKind::StrippedItem(box kind) | kind => kind, }; if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind { render_call_locations(f, cx, item)?; } Ok(()) }) } #[derive(Template)] #[template(path = "item_info.html")] struct ItemInfo { items: Vec, } /// Add extra information about an item such as: /// /// * Stability /// * Deprecated /// * Required features (through the `doc_cfg` feature) fn document_item_info( cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>, ) -> ItemInfo { let items = short_item_info(item, cx, parent); ItemInfo { items } } fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option { let cfg = match (&item.cfg, parent.and_then(|p| p.cfg.as_ref())) { (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), (cfg, _) => cfg.as_deref().cloned(), }; debug!( "Portability {name:?} {item_cfg:?} (parent: {parent:?}) - {parent_cfg:?} = {cfg:?}", name = item.name, item_cfg = item.cfg, parent_cfg = parent.and_then(|p| p.cfg.as_ref()), ); Some(cfg?.render_long_html()) } #[derive(Template)] #[template(path = "short_item_info.html")] enum ShortItemInfo { /// A message describing the deprecation of this item Deprecation { message: String, }, /// The feature corresponding to an unstable item, and optionally /// a tracking issue URL and number. Unstable { feature: String, tracking: Option<(String, u32)>, }, Portability { message: String, }, } /// Render the stability, deprecation and portability information that is displayed at the top of /// the item's documentation. fn short_item_info( item: &clean::Item, cx: &Context<'_>, parent: Option<&clean::Item>, ) -> Vec { let mut extra_info = vec![]; if let Some(depr @ Deprecation { note, since, suggestion: _ }) = item.deprecation(cx.tcx()) { // We display deprecation messages for #[deprecated], but only display // the future-deprecation messages for rustc versions. let mut message = match since { DeprecatedSince::RustcVersion(version) => { if depr.is_in_effect() { format!("Deprecated since {version}") } else { format!("Deprecating in {version}") } } DeprecatedSince::Future => String::from("Deprecating in a future version"), DeprecatedSince::NonStandard(since) => { format!("Deprecated since {}", Escape(since.as_str())) } DeprecatedSince::Unspecified | DeprecatedSince::Err => String::from("Deprecated"), }; if let Some(note) = note { let note = note.as_str(); let mut id_map = cx.id_map.borrow_mut(); let html = MarkdownItemInfo(note, &mut id_map); message.push_str(": "); html.write_into(&mut message).unwrap(); } extra_info.push(ShortItemInfo::Deprecation { message }); } // Render unstable items. But don't render "rustc_private" crates (internal compiler crates). // Those crates are permanently unstable so it makes no sense to render "unstable" everywhere. if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item .stability(cx.tcx()) .as_ref() .filter(|stab| stab.feature != sym::rustc_private) .map(|stab| (stab.level, stab.feature)) { let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) { Some((url.clone(), issue.get())) } else { None }; extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking }); } if let Some(message) = portability(item, parent) { extra_info.push(ShortItemInfo::Portability { message }); } extra_info } // Render the list of items inside one of the sections "Trait Implementations", // "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages). fn render_impls( cx: &Context<'_>, mut w: impl Write, impls: &[&Impl], containing_item: &clean::Item, toggle_open_by_default: bool, ) { let mut rendered_impls = impls .iter() .map(|i| { let did = i.trait_did().unwrap(); let provided_trait_methods = i.inner_impl().provided_trait_methods(cx.tcx()); let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods); let imp = render_impl( cx, i, containing_item, assoc_link, RenderMode::Normal, None, &[], ImplRenderingParameters { show_def_docs: true, show_default_items: true, show_non_assoc_items: true, toggle_open_by_default, }, ); imp.to_string() }) .collect::>(); rendered_impls.sort(); w.write_str(&rendered_impls.join("")).unwrap(); } /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item. fn assoc_href_attr( it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>, ) -> Option { let name = it.name.unwrap(); let item_type = it.type_(); enum Href<'a> { AnchorId(&'a str), Anchor(ItemType), Url(String, ItemType), } let href = match link { AssocItemLink::Anchor(Some(id)) => Href::AnchorId(id), AssocItemLink::Anchor(None) => Href::Anchor(item_type), AssocItemLink::GotoSource(did, provided_methods) => { // We're creating a link from the implementation of an associated item to its // declaration in the trait declaration. let item_type = match item_type { // For historical but not technical reasons, the item type of methods in // trait declarations depends on whether the method is required (`TyMethod`) or // provided (`Method`). ItemType::Method | ItemType::TyMethod => { if provided_methods.contains(&name) { ItemType::Method } else { ItemType::TyMethod } } // For associated types and constants, no such distinction exists. item_type => item_type, }; match href(did.expect_def_id(), cx) { Ok((url, ..)) => Href::Url(url, item_type), // The link is broken since it points to an external crate that wasn't documented. // Do not create any link in such case. This is better than falling back to a // dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item // (that used to happen in older versions). Indeed, in most cases this dummy would // coincide with the `id`. However, it would not always do so. // In general, this dummy would be incorrect: // If the type with the trait impl also had an inherent impl with an assoc. item of // the *same* name as this impl item, the dummy would link to that one even though // those two items are distinct! // In this scenario, the actual `id` of this impl item would be // `#{item_type}.{name}-{n}` for some number `n` (a disambiguator). Err(HrefError::DocumentationNotBuilt) => return None, Err(_) => Href::Anchor(item_type), } } }; let href = fmt::from_fn(move |f| match &href { Href::AnchorId(id) => write!(f, "#{id}"), Href::Url(url, item_type) => { write!(f, "{url}#{item_type}.{name}") } Href::Anchor(item_type) => { write!(f, "#{item_type}.{name}") } }); // If there is no `href` for the reason explained above, simply do not render it which is valid: // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements Some(fmt::from_fn(move |f| write!(f, " href=\"{href}\""))) } #[derive(Debug)] enum AssocConstValue<'a> { // In trait definitions, it is relevant for the public API whether an // associated constant comes with a default value, so even if we cannot // render its value, the presence of a value must be shown using `= _`. TraitDefault(&'a clean::ConstantKind), // In impls, there is no need to show `= _`. Impl(&'a clean::ConstantKind), None, } fn assoc_const( it: &clean::Item, generics: &clean::Generics, ty: &clean::Type, value: AssocConstValue<'_>, link: AssocItemLink<'_>, indent: usize, cx: &Context<'_>, ) -> impl fmt::Display { let tcx = cx.tcx(); fmt::from_fn(move |w| { render_attributes_in_code(w, it, &" ".repeat(indent), cx); write!( w, "{indent}{vis}const {name}{generics}: {ty}", indent = " ".repeat(indent), vis = visibility_print_with_space(it, cx), href = assoc_href_attr(it, link, cx).maybe_display(), name = it.name.as_ref().unwrap(), generics = generics.print(cx), ty = ty.print(cx), )?; if let AssocConstValue::TraitDefault(konst) | AssocConstValue::Impl(konst) = value { // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the // hood which adds noisy underscores and a type suffix to number literals. // This hurts readability in this context especially when more complex expressions // are involved and it doesn't add much of value. // Find a way to print constants here without all that jazz. let repr = konst.value(tcx).unwrap_or_else(|| konst.expr(tcx)); if match value { AssocConstValue::TraitDefault(_) => true, // always show AssocConstValue::Impl(_) => repr != "_", // show if there is a meaningful value to show AssocConstValue::None => unreachable!(), } { write!(w, " = {}", Escape(&repr))?; } } write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline).maybe_display()) }) } fn assoc_type( it: &clean::Item, generics: &clean::Generics, bounds: &[clean::GenericBound], default: Option<&clean::Type>, link: AssocItemLink<'_>, indent: usize, cx: &Context<'_>, ) -> impl fmt::Display { fmt::from_fn(move |w| { write!( w, "{indent}{vis}type {name}{generics}", indent = " ".repeat(indent), vis = visibility_print_with_space(it, cx), href = assoc_href_attr(it, link, cx).maybe_display(), name = it.name.as_ref().unwrap(), generics = generics.print(cx), )?; if !bounds.is_empty() { write!(w, ": {}", print_generic_bounds(bounds, cx))?; } // Render the default before the where-clause which aligns with the new recommended style. See #89122. if let Some(default) = default { write!(w, " = {}", default.print(cx))?; } write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline).maybe_display()) }) } fn assoc_method( meth: &clean::Item, g: &clean::Generics, d: &clean::FnDecl, link: AssocItemLink<'_>, parent: ItemType, cx: &Context<'_>, render_mode: RenderMode, ) -> impl fmt::Display { let tcx = cx.tcx(); let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item"); let name = meth.name.as_ref().unwrap(); let vis = visibility_print_with_space(meth, cx).to_string(); let defaultness = print_default_space(meth.is_default()); // FIXME: Once https://github.com/rust-lang/rust/issues/143874 is implemented, we can remove // this condition. let constness = match render_mode { RenderMode::Normal => print_constness_with_space( &header.constness, meth.stable_since(tcx), meth.const_stability(tcx), ), RenderMode::ForDeref { .. } => "", }; fmt::from_fn(move |w| { let asyncness = header.asyncness.print_with_space(); let safety = header.safety.print_with_space(); let abi = print_abi_with_space(header.abi).to_string(); let href = assoc_href_attr(meth, link, cx).maybe_display(); // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`. let generics_len = format!("{:#}", g.print(cx)).len(); let mut header_len = "fn ".len() + vis.len() + defaultness.len() + constness.len() + asyncness.len() + safety.len() + abi.len() + name.as_str().len() + generics_len; let notable_traits = notable_traits_button(&d.output, cx).maybe_display(); let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; let indent_str = " "; render_attributes_in_code(w, meth, indent_str, cx); (4, indent_str, Ending::NoNewline) } else { render_attributes_in_code(w, meth, "", cx); (0, "", Ending::Newline) }; write!( w, "{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}", indent = indent_str, generics = g.print(cx), decl = d.full_print(header_len, indent, cx), where_clause = print_where_clause(g, cx, indent, end_newline).maybe_display(), ) }) } /// Writes a span containing the versions at which an item became stable and/or const-stable. For /// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would /// write a span containing "1.0.0 (const: 1.45.0)". /// /// Returns `None` if there is no stability annotation to be rendered. /// /// Stability and const-stability are considered separately. If the item is unstable, no version /// will be written. If the item is const-unstable, "const: unstable" will be appended to the /// span, with a link to the tracking issue if present. If an item's stability or const-stability /// version matches the version of its enclosing item, that version will be omitted. /// /// Note that it is possible for an unstable function to be const-stable. In that case, the span /// will include the const-stable version, but no stable version will be emitted, as a natural /// consequence of the above rules. fn render_stability_since_raw_with_extra( stable_version: Option, const_stability: Option, extra_class: &str, ) -> Option { let mut title = String::new(); let mut stability = String::new(); if let Some(version) = stable_version.and_then(|version| since_to_string(&version)) { stability.push_str(&version); title.push_str(&format!("Stable since Rust version {version}")); } let const_title_and_stability = match const_stability { Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. }) => { since_to_string(&since) .map(|since| (format!("const since {since}"), format!("const: {since}"))) } Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => { if stable_version.is_none() { // don't display const unstable if entirely unstable None } else { let unstable = if let Some(n) = issue { format!( "unstable" ) } else { String::from("unstable") }; Some((String::from("const unstable"), format!("const: {unstable}"))) } } _ => None, }; if let Some((const_title, const_stability)) = const_title_and_stability { if !title.is_empty() { title.push_str(&format!(", {const_title}")); } else { title.push_str(&const_title); } if !stability.is_empty() { stability.push_str(&format!(" ({const_stability})")); } else { stability.push_str(&const_stability); } } (!stability.is_empty()).then_some(fmt::from_fn(move |w| { write!(w, r#"{stability}"#) })) } fn since_to_string(since: &StableSince) -> Option { match since { StableSince::Version(since) => Some(since.to_string()), StableSince::Current => Some(RustcVersion::CURRENT.to_string()), StableSince::Err(_) => None, } } #[inline] fn render_stability_since_raw( ver: Option, const_stability: Option, ) -> Option { render_stability_since_raw_with_extra(ver, const_stability, "") } fn render_assoc_item( item: &clean::Item, link: AssocItemLink<'_>, parent: ItemType, cx: &Context<'_>, render_mode: RenderMode, ) -> impl fmt::Display { fmt::from_fn(move |f| match &item.kind { clean::StrippedItem(..) => Ok(()), clean::RequiredMethodItem(m) | clean::MethodItem(m, _) => { assoc_method(item, &m.generics, &m.decl, link, parent, cx, render_mode).fmt(f) } clean::RequiredAssocConstItem(generics, ty) => assoc_const( item, generics, ty, AssocConstValue::None, link, if parent == ItemType::Trait { 4 } else { 0 }, cx, ) .fmt(f), clean::ProvidedAssocConstItem(ci) => assoc_const( item, &ci.generics, &ci.type_, AssocConstValue::TraitDefault(&ci.kind), link, if parent == ItemType::Trait { 4 } else { 0 }, cx, ) .fmt(f), clean::ImplAssocConstItem(ci) => assoc_const( item, &ci.generics, &ci.type_, AssocConstValue::Impl(&ci.kind), link, if parent == ItemType::Trait { 4 } else { 0 }, cx, ) .fmt(f), clean::RequiredAssocTypeItem(generics, bounds) => assoc_type( item, generics, bounds, None, link, if parent == ItemType::Trait { 4 } else { 0 }, cx, ) .fmt(f), clean::AssocTypeItem(ty, bounds) => assoc_type( item, &ty.generics, bounds, Some(ty.item_type.as_ref().unwrap_or(&ty.type_)), link, if parent == ItemType::Trait { 4 } else { 0 }, cx, ) .fmt(f), _ => panic!("render_assoc_item called on non-associated-item"), }) } struct CodeAttribute(String); fn render_code_attribute(prefix: &str, code_attr: CodeAttribute, w: &mut impl fmt::Write) { write!( w, "
{prefix}{attr}
", prefix = prefix, attr = code_attr.0 ) .unwrap(); } // When an attribute is rendered inside a tag, it is formatted using // a div to produce a newline after it. fn render_attributes_in_code( w: &mut impl fmt::Write, it: &clean::Item, prefix: &str, cx: &Context<'_>, ) { for attr in it.attributes(cx.tcx(), cx.cache()) { render_code_attribute(prefix, CodeAttribute(attr), w); } } /// used for type aliases to only render their `repr` attribute. fn render_repr_attributes_in_code( w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId, item_type: ItemType, ) { if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) { render_code_attribute("", CodeAttribute(repr), w); } } #[derive(Copy, Clone)] enum AssocItemLink<'a> { Anchor(Option<&'a str>), GotoSource(ItemId, &'a FxIndexSet), } impl<'a> AssocItemLink<'a> { fn anchor(&self, id: &'a str) -> Self { match *self { AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(id)), ref other => *other, } } } fn write_section_heading( title: impl fmt::Display, id: &str, extra_class: Option<&str>, extra: impl fmt::Display, ) -> impl fmt::Display { fmt::from_fn(move |w| { let (extra_class, whitespace) = match extra_class { Some(extra) => (extra, " "), None => ("", ""), }; write!( w, "

\ {title}\ §\

{extra}", ) }) } fn write_impl_section_heading(title: impl fmt::Display, id: &str) -> impl fmt::Display { write_section_heading(title, id, None, "") } fn render_all_impls( mut w: impl Write, cx: &Context<'_>, containing_item: &clean::Item, concrete: &[&Impl], synthetic: &[&Impl], blanket_impl: &[&Impl], ) { let impls = { let mut buf = String::new(); render_impls(cx, &mut buf, concrete, containing_item, true); buf }; if !impls.is_empty() { write!( w, "{}
{impls}
", write_impl_section_heading("Trait Implementations", "trait-implementations") ) .unwrap(); } if !synthetic.is_empty() { write!( w, "{}
", write_impl_section_heading("Auto Trait Implementations", "synthetic-implementations",) ) .unwrap(); render_impls(cx, &mut w, synthetic, containing_item, false); w.write_str("
").unwrap(); } if !blanket_impl.is_empty() { write!( w, "{}
", write_impl_section_heading("Blanket Implementations", "blanket-implementations") ) .unwrap(); render_impls(cx, &mut w, blanket_impl, containing_item, false); w.write_str("
").unwrap(); } } fn render_assoc_items( cx: &Context<'_>, containing_item: &clean::Item, it: DefId, what: AssocItemRender<'_>, ) -> impl fmt::Display { fmt::from_fn(move |f| { let mut derefs = DefIdSet::default(); derefs.insert(it); render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs); Ok(()) }) } fn render_assoc_items_inner( mut w: &mut dyn fmt::Write, cx: &Context<'_>, containing_item: &clean::Item, it: DefId, what: AssocItemRender<'_>, derefs: &mut DefIdSet, ) { info!("Documenting associated items of {:?}", containing_item.name); let cache = &cx.shared.cache; let Some(v) = cache.impls.get(&it) else { return }; let (mut non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none()); if !non_trait.is_empty() { let render_mode = what.render_mode(); let class_html = what .class() .map(|class| fmt::from_fn(move |f| write!(f, r#" class="{class}""#))) .maybe_display(); let (section_heading, id) = match what { AssocItemRender::All => ( Either::Left(write_impl_section_heading("Implementations", "implementations")), Cow::Borrowed("implementations-list"), ), AssocItemRender::DerefFor { trait_, type_, .. } => { let id = cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx)))); // the `impls.get` above only looks at the outermost type, // and the Deref impl may only be implemented for certain // values of generic parameters. // for example, if an item impls `Deref<[u8]>`, // we should not show methods from `[MaybeUninit]`. // this `retain` filters out any instances where // the types do not line up perfectly. non_trait.retain(|impl_| { type_.is_doc_subtype_of(&impl_.inner_impl().for_, &cx.shared.cache) }); let derived_id = cx.derive_id(&id); if let Some(def_id) = type_.def_id(cx.cache()) { cx.deref_id_map.borrow_mut().insert(def_id, id.clone()); } ( Either::Right(fmt::from_fn(move |f| { write!( f, "
{}", write_impl_section_heading( fmt::from_fn(|f| write!( f, "Methods from {trait_}<Target = {type_}>", trait_ = trait_.print(cx), type_ = type_.print(cx), )), &id, ) ) })), Cow::Owned(derived_id), ) } }; let mut impls_buf = String::new(); for i in &non_trait { write_str( &mut impls_buf, format_args!( "{}", render_impl( cx, i, containing_item, AssocItemLink::Anchor(None), render_mode, None, &[], ImplRenderingParameters { show_def_docs: true, show_default_items: true, show_non_assoc_items: true, toggle_open_by_default: true, }, ) ), ); } if !impls_buf.is_empty() { write!( w, "{section_heading}
{impls_buf}
{}", matches!(what, AssocItemRender::DerefFor { .. }) .then_some("
") .maybe_display(), ) .unwrap(); } } if !traits.is_empty() { let deref_impl = traits.iter().find(|t| t.trait_did() == cx.tcx().lang_items().deref_trait()); if let Some(impl_) = deref_impl { let has_deref_mut = traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs); } // If we were already one level into rendering deref methods, we don't want to render // anything after recursing into any further deref methods above. if let AssocItemRender::DerefFor { .. } = what { return; } let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = traits.into_iter().partition(|t| t.inner_impl().kind.is_auto()); let (blanket_impl, concrete): (Vec<&Impl>, _) = concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket()); render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl); } } /// `derefs` is the set of all deref targets that have already been handled. fn render_deref_methods( mut w: impl Write, cx: &Context<'_>, impl_: &Impl, container_item: &clean::Item, deref_mut: bool, derefs: &mut DefIdSet, ) { let cache = cx.cache(); let deref_type = impl_.inner_impl().trait_.as_ref().unwrap(); let (target, real_target) = impl_ .inner_impl() .items .iter() .find_map(|item| match item.kind { clean::AssocTypeItem(box ref t, _) => Some(match *t { clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_), _ => (&t.type_, &t.type_), }), _ => None, }) .expect("Expected associated type binding"); debug!( "Render deref methods for {for_:#?}, target {target:#?}", for_ = impl_.inner_impl().for_ ); let what = AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut }; if let Some(did) = target.def_id(cache) { if let Some(type_did) = impl_.inner_impl().for_.def_id(cache) { // `impl Deref for S` if did == type_did || !derefs.insert(did) { // Avoid infinite cycles return; } } render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs); } else if let Some(prim) = target.primitive_type() && let Some(&did) = cache.primitive_locations.get(&prim) { render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs); } } fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool { let self_type_opt = match item.kind { clean::MethodItem(ref method, _) => method.decl.receiver_type(), clean::RequiredMethodItem(ref method) => method.decl.receiver_type(), _ => None, }; if let Some(self_ty) = self_type_opt { let (by_mut_ref, by_box, by_value) = match *self_ty { clean::Type::BorrowedRef { mutability, .. } => { (mutability == Mutability::Mut, false, false) } clean::Type::Path { ref path } => { (false, Some(path.def_id()) == tcx.lang_items().owned_box(), false) } clean::Type::SelfTy => (false, false, true), _ => (false, false, false), }; (deref_mut_ || !by_mut_ref) && !by_box && !by_value } else { false } } fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option { if ty.is_unit() { // Very common fast path. return None; } let did = ty.def_id(cx.cache())?; // Box has pass-through impls for Read, Write, Iterator, and Future when the // boxed type implements one of those. We don't want to treat every Box return // as being notably an Iterator (etc), though, so we exempt it. Pin has the same // issue, with a pass-through impl for Future. if Some(did) == cx.tcx().lang_items().owned_box() || Some(did) == cx.tcx().lang_items().pin_type() { return None; } let impls = cx.cache().impls.get(&did)?; let has_notable_trait = impls .iter() .map(Impl::inner_impl) .filter(|impl_| { impl_.polarity == ty::ImplPolarity::Positive // Two different types might have the same did, // without actually being the same. && ty.is_doc_subtype_of(&impl_.for_, cx.cache()) }) .filter_map(|impl_| impl_.trait_.as_ref()) .filter_map(|trait_| cx.cache().traits.get(&trait_.def_id())) .any(|t| t.is_notable_trait(cx.tcx())); has_notable_trait.then(|| { cx.types_with_notable_traits.borrow_mut().insert(ty.clone()); fmt::from_fn(|f| { write!( f, " ", ty = Escape(&format!("{:#}", ty.print(cx))), ) }) }) } fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { let mut out = String::new(); let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this"); let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this"); for i in impls { let impl_ = i.inner_impl(); if impl_.polarity != ty::ImplPolarity::Positive { continue; } if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) { // Two different types might have the same did, // without actually being the same. continue; } if let Some(trait_) = &impl_.trait_ { let trait_did = trait_.def_id(); if cx.cache().traits.get(&trait_did).is_some_and(|t| t.is_notable_trait(cx.tcx())) { if out.is_empty() { write_str( &mut out, format_args!( "

Notable traits for {}

\
",
                            impl_.for_.print(cx)
                        ),
                    );
                }

                write_str(
                    &mut out,
                    format_args!("
{}
", impl_.print(false, cx)), ); for it in &impl_.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { let empty_set = FxIndexSet::default(); let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); write_str( &mut out, format_args!( "
{};
", assoc_type( it, &tydef.generics, &[], // intentionally leaving out bounds Some(&tydef.type_), src_link, 0, cx, ) ), ); } } } } } if out.is_empty() { out.push_str("
"); } (format!("{:#}", ty.print(cx)), out) } fn notable_traits_json<'a>(tys: impl Iterator, cx: &Context<'_>) -> String { let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect(); mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2)); struct NotableTraitsMap(Vec<(String, String)>); impl Serialize for NotableTraitsMap { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut map = serializer.serialize_map(Some(self.0.len()))?; for item in &self.0 { map.serialize_entry(&item.0, &item.1)?; } map.end() } } serde_json::to_string(&NotableTraitsMap(mp)) .expect("serialize (string, string) -> json object cannot fail") } #[derive(Clone, Copy, Debug)] struct ImplRenderingParameters { show_def_docs: bool, show_default_items: bool, /// Whether or not to show methods. show_non_assoc_items: bool, toggle_open_by_default: bool, } fn render_impl( cx: &Context<'_>, i: &Impl, parent: &clean::Item, link: AssocItemLink<'_>, render_mode: RenderMode, use_absolute: Option, aliases: &[String], rendering_params: ImplRenderingParameters, ) -> impl fmt::Display { fmt::from_fn(move |w| { let cache = &cx.shared.cache; let traits = &cache.traits; let trait_ = i.trait_did().map(|did| &traits[&did]); let mut close_tags = >::with_capacity(2); // For trait implementations, the `interesting` output contains all methods that have doc // comments, and the `boring` output contains all methods that do not. The distinction is // used to allow hiding the boring methods. // `containing_item` is used for rendering stability info. If the parent is a trait impl, // `containing_item` will the grandparent, since trait impls can't have stability attached. fn doc_impl_item( boring: impl fmt::Write, interesting: impl fmt::Write, cx: &Context<'_>, item: &clean::Item, parent: &clean::Item, link: AssocItemLink<'_>, render_mode: RenderMode, is_default_item: bool, trait_: Option<&clean::Trait>, rendering_params: ImplRenderingParameters, ) -> fmt::Result { let item_type = item.type_(); let name = item.name.as_ref().unwrap(); let render_method_item = rendering_params.show_non_assoc_items && match render_mode { RenderMode::Normal => true, RenderMode::ForDeref { mut_: deref_mut_ } => { should_render_item(item, deref_mut_, cx.tcx()) } }; let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" }; let mut doc_buffer = String::new(); let mut info_buffer = String::new(); let mut short_documented = true; if render_method_item { if !is_default_item { if let Some(t) = trait_ { // The trait item may have been stripped so we might not // find any documentation or stability for it. if let Some(it) = t.items.iter().find(|i| i.name == item.name) { // We need the stability of the item from the trait // because impls can't have a stability. if !item.doc_value().is_empty() { document_item_info(cx, it, Some(parent)) .render_into(&mut info_buffer) .unwrap(); write_str( &mut doc_buffer, format_args!("{}", document_full(item, cx, HeadingOffset::H5)), ); short_documented = false; } else { // In case the item isn't documented, // provide short documentation from the trait. write_str( &mut doc_buffer, format_args!( "{}", document_short( it, cx, link, parent, rendering_params.show_def_docs, ) ), ); } } } else { document_item_info(cx, item, Some(parent)) .render_into(&mut info_buffer) .unwrap(); if rendering_params.show_def_docs { write_str( &mut doc_buffer, format_args!("{}", document_full(item, cx, HeadingOffset::H5)), ); short_documented = false; } } } else { write_str( &mut doc_buffer, format_args!( "{}", document_short(item, cx, link, parent, rendering_params.show_def_docs) ), ); } } let mut w = if short_documented && trait_.is_some() { Either::Left(interesting) } else { Either::Right(boring) }; let toggled = !doc_buffer.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; write!(w, "
")?; } match &item.kind { clean::MethodItem(..) | clean::RequiredMethodItem(_) => { // Only render when the method is not static or we allow static methods if render_method_item { let id = cx.derive_id(format!("{item_type}.{name}")); let source_id = trait_ .and_then(|trait_| { trait_ .items .iter() .find(|item| item.name.map(|n| n == *name).unwrap_or(false)) }) .map(|item| format!("{}.{name}", item.type_())); write!( w, "
\ {}", render_rightside(cx, item, render_mode) )?; if trait_.is_some() { // Anchors are only used on trait impls. write!(w, "§")?; } write!( w, "

{}

", render_assoc_item( item, link.anchor(source_id.as_ref().unwrap_or(&id)), ItemType::Impl, cx, render_mode, ), )?; } } clean::RequiredAssocConstItem(generics, ty) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); write!( w, "
\ {}", render_rightside(cx, item, render_mode) )?; if trait_.is_some() { // Anchors are only used on trait impls. write!(w, "§")?; } write!( w, "

{}

", assoc_const( item, generics, ty, AssocConstValue::None, link.anchor(if trait_.is_some() { &source_id } else { &id }), 0, cx, ), )?; } clean::ProvidedAssocConstItem(ci) | clean::ImplAssocConstItem(ci) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); write!( w, "
\ {}", render_rightside(cx, item, render_mode), )?; if trait_.is_some() { // Anchors are only used on trait impls. write!(w, "§")?; } write!( w, "

{}

", assoc_const( item, &ci.generics, &ci.type_, match item.kind { clean::ProvidedAssocConstItem(_) => AssocConstValue::TraitDefault(&ci.kind), clean::ImplAssocConstItem(_) => AssocConstValue::Impl(&ci.kind), _ => unreachable!(), }, link.anchor(if trait_.is_some() { &source_id } else { &id }), 0, cx, ), )?; } clean::RequiredAssocTypeItem(generics, bounds) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); write!( w, "
\ {}", render_rightside(cx, item, render_mode), )?; if trait_.is_some() { // Anchors are only used on trait impls. write!(w, "§")?; } write!( w, "

{}

", assoc_type( item, generics, bounds, None, link.anchor(if trait_.is_some() { &source_id } else { &id }), 0, cx, ), )?; } clean::AssocTypeItem(tydef, _bounds) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); write!( w, "
\ {}", render_rightside(cx, item, render_mode), )?; if trait_.is_some() { // Anchors are only used on trait impls. write!(w, "§")?; } write!( w, "

{}

", assoc_type( item, &tydef.generics, &[], // intentionally leaving out bounds Some(tydef.item_type.as_ref().unwrap_or(&tydef.type_)), link.anchor(if trait_.is_some() { &source_id } else { &id }), 0, cx, ), )?; } clean::StrippedItem(..) => return Ok(()), _ => panic!("can't make docs for trait item with name {:?}", item.name), } w.write_str(&info_buffer)?; if toggled { write!(w, "
{doc_buffer}
")?; } Ok(()) } let mut impl_items = String::new(); let mut default_impl_items = String::new(); let impl_ = i.inner_impl(); // Impl items are grouped by kinds: // // 1. Constants // 2. Types // 3. Functions // // This order is because you can have associated constants used in associated types (like array // length), and both in associated functions. So with this order, when reading from top to // bottom, you should see items definitions before they're actually used most of the time. let mut assoc_types = Vec::new(); let mut methods = Vec::new(); if !impl_.is_negative_trait_impl() { for trait_item in &impl_.items { match trait_item.kind { clean::MethodItem(..) | clean::RequiredMethodItem(_) => { methods.push(trait_item) } clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => { assoc_types.push(trait_item) } clean::RequiredAssocConstItem(..) | clean::ProvidedAssocConstItem(_) | clean::ImplAssocConstItem(_) => { // We render it directly since they're supposed to come first. doc_impl_item( &mut default_impl_items, &mut impl_items, cx, trait_item, if trait_.is_some() { &i.impl_item } else { parent }, link, render_mode, false, trait_, rendering_params, )?; } _ => {} } } for assoc_type in assoc_types { doc_impl_item( &mut default_impl_items, &mut impl_items, cx, assoc_type, if trait_.is_some() { &i.impl_item } else { parent }, link, render_mode, false, trait_, rendering_params, )?; } for method in methods { doc_impl_item( &mut default_impl_items, &mut impl_items, cx, method, if trait_.is_some() { &i.impl_item } else { parent }, link, render_mode, false, trait_, rendering_params, )?; } } fn render_default_items( mut boring: impl fmt::Write, mut interesting: impl fmt::Write, cx: &Context<'_>, t: &clean::Trait, i: &clean::Impl, parent: &clean::Item, render_mode: RenderMode, rendering_params: ImplRenderingParameters, ) -> fmt::Result { for trait_item in &t.items { // Skip over any default trait items that are impossible to reference // (e.g. if it has a `Self: Sized` bound on an unsized type). if let Some(impl_def_id) = parent.item_id.as_def_id() && let Some(trait_item_def_id) = trait_item.item_id.as_def_id() && cx.tcx().is_impossible_associated_item((impl_def_id, trait_item_def_id)) { continue; } let n = trait_item.name; if i.items.iter().any(|m| m.name == n) { continue; } let did = i.trait_.as_ref().unwrap().def_id(); let provided_methods = i.provided_trait_methods(cx.tcx()); let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods); doc_impl_item( &mut boring, &mut interesting, cx, trait_item, parent, assoc_link, render_mode, true, Some(t), rendering_params, )?; } Ok(()) } // If we've implemented a trait, then also emit documentation for all // default items which weren't overridden in the implementation block. // We don't emit documentation for default items if they appear in the // Implementations on Foreign Types or Implementors sections. if rendering_params.show_default_items && let Some(t) = trait_ && !impl_.is_negative_trait_impl() { render_default_items( &mut default_impl_items, &mut impl_items, cx, t, impl_, &i.impl_item, render_mode, rendering_params, )?; } if render_mode == RenderMode::Normal { let toggled = !(impl_items.is_empty() && default_impl_items.is_empty()); if toggled { close_tags.push(""); write!( w, "
\ ", if rendering_params.toggle_open_by_default { " open" } else { "" } )?; } let (before_dox, after_dox) = i .impl_item .opt_doc_value() .map(|dox| { Markdown { content: &dox, links: &i.impl_item.links(cx), ids: &mut cx.id_map.borrow_mut(), error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, heading_offset: HeadingOffset::H4, } .split_summary_and_content() }) .unwrap_or((None, None)); write!( w, "{}", render_impl_summary( cx, i, parent, rendering_params.show_def_docs, use_absolute, aliases, before_dox.as_deref(), trait_.is_none() && impl_.items.is_empty(), ) )?; if toggled { w.write_str("")?; } if before_dox.is_some() && let Some(after_dox) = after_dox { write!(w, "
{after_dox}
")?; } if !default_impl_items.is_empty() || !impl_items.is_empty() { w.write_str("
")?; close_tags.push("
"); } } if !default_impl_items.is_empty() || !impl_items.is_empty() { w.write_str(&default_impl_items)?; w.write_str(&impl_items)?; } for tag in close_tags.into_iter().rev() { w.write_str(tag)?; } Ok(()) }) } // Render the items that appear on the right side of methods, impls, and // associated types. For example "1.0.0 (const: 1.39.0) · source". fn render_rightside( cx: &Context<'_>, item: &clean::Item, render_mode: RenderMode, ) -> impl fmt::Display { let tcx = cx.tcx(); fmt::from_fn(move |w| { // FIXME: Once https://github.com/rust-lang/rust/issues/143874 is implemented, we can remove // this condition. let const_stability = match render_mode { RenderMode::Normal => item.const_stability(tcx), RenderMode::ForDeref { .. } => None, }; let src_href = cx.src_href(item); let stability = render_stability_since_raw_with_extra( item.stable_since(tcx), const_stability, if src_href.is_some() { "" } else { " rightside" }, ); match (stability, src_href) { (Some(stability), Some(link)) => { write!( w, "{stability} · Source", ) } (Some(stability), None) => { write!(w, "{stability}") } (None, Some(link)) => { write!(w, "Source") } (None, None) => Ok(()), } }) } fn render_impl_summary( cx: &Context<'_>, i: &Impl, parent: &clean::Item, show_def_docs: bool, use_absolute: Option, // This argument is used to reference same type with different paths to avoid duplication // in documentation pages for trait with automatic implementations like "Send" and "Sync". aliases: &[String], doc: Option<&str>, impl_is_empty: bool, ) -> impl fmt::Display { fmt::from_fn(move |w| { let inner_impl = i.inner_impl(); let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id)); let aliases = (!aliases.is_empty()) .then_some(fmt::from_fn(|f| { write!(f, " data-aliases=\"{}\"", fmt::from_fn(|f| aliases.iter().joined(",", f))) })) .maybe_display(); write!( w, "
\ {}\ §\

", render_rightside(cx, &i.impl_item, RenderMode::Normal) )?; if let Some(use_absolute) = use_absolute { write!(w, "{}", inner_impl.print(use_absolute, cx))?; if show_def_docs { for it in &inner_impl.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { write!( w, "
{};
", assoc_type( it, &tydef.generics, &[], // intentionally leaving out bounds Some(&tydef.type_), AssocItemLink::Anchor(None), 0, cx, ) )?; } } } } else { write!(w, "{}", inner_impl.print(false, cx))?; } w.write_str("

")?; let is_trait = inner_impl.trait_.is_some(); if is_trait && let Some(portability) = portability(&i.impl_item, Some(parent)) { write!( w, "\
{portability}
\
", )?; } if let Some(doc) = doc { if impl_is_empty { w.write_str( "
\
This impl block contains no items.
\
", )?; } write!(w, "
{doc}
")?; } w.write_str("
") }) } pub(crate) fn small_url_encode(s: String) -> String { // These characters don't need to be escaped in a URI. // See https://url.spec.whatwg.org/#query-percent-encode-set // and https://url.spec.whatwg.org/#urlencoded-parsing // and https://url.spec.whatwg.org/#url-code-points fn dont_escape(c: u8) -> bool { c.is_ascii_alphanumeric() || c == b'-' || c == b'_' || c == b'.' || c == b',' || c == b'~' || c == b'!' || c == b'\'' || c == b'(' || c == b')' || c == b'*' || c == b'/' || c == b';' || c == b':' || c == b'?' // As described in urlencoded-parsing, the // first `=` is the one that separates key from // value. Following `=`s are part of the value. || c == b'=' } let mut st = String::new(); let mut last_match = 0; for (idx, b) in s.bytes().enumerate() { if dont_escape(b) { continue; } if last_match != idx { // Invariant: `idx` must be the first byte in a character at this point. st += &s[last_match..idx]; } if b == b' ' { // URL queries are decoded with + replaced with SP. // While the same is not true for hashes, rustdoc only needs to be // consistent with itself when encoding them. st += "+"; } else { write!(st, "%{b:02X}").unwrap(); } // Invariant: if the current byte is not at the start of a multi-byte character, // we need to get down here so that when the next turn of the loop comes around, // last_match winds up equalling idx. // // In other words, dont_escape must always return `false` in multi-byte character. last_match = idx + 1; } if last_match != 0 { st += &s[last_match..]; st } else { s } } fn get_id_for_impl(tcx: TyCtxt<'_>, impl_id: ItemId) -> String { use rustc_middle::ty::print::with_forced_trimmed_paths; let (type_, trait_) = match impl_id { ItemId::Auto { trait_, for_ } => { let ty = tcx.type_of(for_).skip_binder(); (ty, Some(ty::TraitRef::new(tcx, trait_, [ty]))) } ItemId::Blanket { impl_id, .. } | ItemId::DefId(impl_id) => { if let Some(trait_ref) = tcx.impl_trait_ref(impl_id) { let trait_ref = trait_ref.skip_binder(); (trait_ref.self_ty(), Some(trait_ref)) } else { (tcx.type_of(impl_id).skip_binder(), None) } } }; with_forced_trimmed_paths!(small_url_encode(if let Some(trait_) = trait_ { format!("impl-{trait_}-for-{type_}", trait_ = trait_.print_only_trait_path()) } else { format!("impl-{type_}") })) } fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { match item.kind { clean::ItemKind::ImplItem(ref i) if i.trait_.is_some() => { // Alternative format produces no URLs, // so this parameter does nothing. Some((format!("{:#}", i.for_.print(cx)), get_id_for_impl(cx.tcx(), item.item_id))) } _ => None, } } /// Returns the list of implementations for the primitive reference type, filtering out any /// implementations that are on concrete or partially generic types, only keeping implementations /// of the form `impl Trait for &T`. pub(crate) fn get_filtered_impls_for_reference<'a>( shared: &'a SharedContext<'_>, it: &clean::Item, ) -> (Vec<&'a Impl>, Vec<&'a Impl>, Vec<&'a Impl>) { let def_id = it.item_id.expect_def_id(); // If the reference primitive is somehow not defined, exit early. let Some(v) = shared.cache.impls.get(&def_id) else { return (Vec::new(), Vec::new(), Vec::new()); }; // Since there is no "direct implementation" on the reference primitive type, we filter out // every implementation which isn't a trait implementation. let traits = v.iter().filter(|i| i.inner_impl().trait_.is_some()); let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = traits.partition(|t| t.inner_impl().kind.is_auto()); let (blanket_impl, concrete): (Vec<&Impl>, _) = concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket()); // Now we keep only references over full generic types. let concrete: Vec<_> = concrete .into_iter() .filter(|t| match t.inner_impl().for_ { clean::Type::BorrowedRef { ref type_, .. } => type_.is_full_generic(), _ => false, }) .collect(); (concrete, synthetic, blanket_impl) } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum ItemSection { Reexports, PrimitiveTypes, Modules, Macros, Structs, Enums, Constants, Statics, Traits, Functions, TypeAliases, Unions, Implementations, TypeMethods, Methods, StructFields, Variants, AssociatedTypes, AssociatedConstants, ForeignTypes, Keywords, Attributes, AttributeMacros, DeriveMacros, TraitAliases, } impl ItemSection { const ALL: &'static [Self] = { use ItemSection::*; // NOTE: The order here affects the order in the UI. // Keep this synchronized with addSidebarItems in main.js &[ Reexports, PrimitiveTypes, Modules, Macros, Structs, Enums, Constants, Statics, Traits, Functions, TypeAliases, Unions, Implementations, TypeMethods, Methods, StructFields, Variants, AssociatedTypes, AssociatedConstants, ForeignTypes, Keywords, Attributes, AttributeMacros, DeriveMacros, TraitAliases, ] }; fn id(self) -> &'static str { match self { Self::Reexports => "reexports", Self::Modules => "modules", Self::Structs => "structs", Self::Unions => "unions", Self::Enums => "enums", Self::Functions => "functions", Self::TypeAliases => "types", Self::Statics => "statics", Self::Constants => "constants", Self::Traits => "traits", Self::Implementations => "impls", Self::TypeMethods => "tymethods", Self::Methods => "methods", Self::StructFields => "fields", Self::Variants => "variants", Self::Macros => "macros", Self::PrimitiveTypes => "primitives", Self::AssociatedTypes => "associated-types", Self::AssociatedConstants => "associated-consts", Self::ForeignTypes => "foreign-types", Self::Keywords => "keywords", Self::Attributes => "attributes", Self::AttributeMacros => "attributes", Self::DeriveMacros => "derives", Self::TraitAliases => "trait-aliases", } } fn name(self) -> &'static str { match self { Self::Reexports => "Re-exports", Self::Modules => "Modules", Self::Structs => "Structs", Self::Unions => "Unions", Self::Enums => "Enums", Self::Functions => "Functions", Self::TypeAliases => "Type Aliases", Self::Statics => "Statics", Self::Constants => "Constants", Self::Traits => "Traits", Self::Implementations => "Implementations", Self::TypeMethods => "Type Methods", Self::Methods => "Methods", Self::StructFields => "Struct Fields", Self::Variants => "Variants", Self::Macros => "Macros", Self::PrimitiveTypes => "Primitive Types", Self::AssociatedTypes => "Associated Types", Self::AssociatedConstants => "Associated Constants", Self::ForeignTypes => "Foreign Types", Self::Keywords => "Keywords", Self::Attributes => "Attributes", Self::AttributeMacros => "Attribute Macros", Self::DeriveMacros => "Derive Macros", Self::TraitAliases => "Trait Aliases", } } } fn item_ty_to_section(ty: ItemType) -> ItemSection { match ty { ItemType::ExternCrate | ItemType::Import => ItemSection::Reexports, ItemType::Module => ItemSection::Modules, ItemType::Struct => ItemSection::Structs, ItemType::Union => ItemSection::Unions, ItemType::Enum => ItemSection::Enums, ItemType::Function => ItemSection::Functions, ItemType::TypeAlias => ItemSection::TypeAliases, ItemType::Static => ItemSection::Statics, ItemType::Constant => ItemSection::Constants, ItemType::Trait => ItemSection::Traits, ItemType::Impl => ItemSection::Implementations, ItemType::TyMethod => ItemSection::TypeMethods, ItemType::Method => ItemSection::Methods, ItemType::StructField => ItemSection::StructFields, ItemType::Variant => ItemSection::Variants, ItemType::Macro => ItemSection::Macros, ItemType::Primitive => ItemSection::PrimitiveTypes, ItemType::AssocType => ItemSection::AssociatedTypes, ItemType::AssocConst => ItemSection::AssociatedConstants, ItemType::ForeignType => ItemSection::ForeignTypes, ItemType::Keyword => ItemSection::Keywords, ItemType::Attribute => ItemSection::Attributes, ItemType::ProcAttribute => ItemSection::AttributeMacros, ItemType::ProcDerive => ItemSection::DeriveMacros, ItemType::TraitAlias => ItemSection::TraitAliases, } } /// Returns a list of all paths used in the type. /// This is used to help deduplicate imported impls /// for reexported types. If any of the contained /// types are re-exported, we don't use the corresponding /// entry from the js file, as inlining will have already /// picked up the impl fn collect_paths_for_type(first_ty: &clean::Type, cache: &Cache) -> Vec { let mut out = Vec::new(); let mut visited = FxHashSet::default(); let mut work = VecDeque::new(); let mut process_path = |did: DefId| { let get_extern = || cache.external_paths.get(&did).map(|s| &s.0); let fqp = cache.exact_paths.get(&did).or_else(get_extern); if let Some(path) = fqp { out.push(join_path_syms(path)); } }; work.push_back(first_ty); while let Some(ty) = work.pop_front() { if !visited.insert(ty) { continue; } match ty { clean::Type::Path { path } => process_path(path.def_id()), clean::Type::Tuple(tys) => { work.extend(tys.iter()); } clean::Type::Slice(ty) => { work.push_back(ty); } clean::Type::Array(ty, _) => { work.push_back(ty); } clean::Type::RawPointer(_, ty) => { work.push_back(ty); } clean::Type::BorrowedRef { type_, .. } => { work.push_back(type_); } clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => { work.push_back(self_type); if let Some(trait_) = trait_ { process_path(trait_.def_id()); } } _ => {} } } out } const MAX_FULL_EXAMPLES: usize = 5; const NUM_VISIBLE_LINES: usize = 10; /// Generates the HTML for example call locations generated via the --scrape-examples flag. fn render_call_locations( mut w: W, cx: &Context<'_>, item: &clean::Item, ) -> fmt::Result { let tcx = cx.tcx(); let def_id = item.item_id.expect_def_id(); let key = tcx.def_path_hash(def_id); let Some(call_locations) = cx.shared.call_locations.get(&key) else { return Ok(()) }; // Generate a unique ID so users can link to this section for a given method let id = cx.derive_id("scraped-examples"); write!( &mut w, "
\ \
\ Examples found in repository\ ?\
", root_path = cx.root_path(), id = id )?; // Create a URL to a particular location in a reverse-dependency's source file let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) { let (line_lo, line_hi) = loc.call_expr.line_span; let (anchor, title) = if line_lo == line_hi { ((line_lo + 1).to_string(), format!("line {}", line_lo + 1)) } else { ( format!("{}-{}", line_lo + 1, line_hi + 1), format!("lines {}-{}", line_lo + 1, line_hi + 1), ) }; let url = format!("{}{}#{anchor}", cx.root_path(), call_data.url); (url, title) }; // Generate the HTML for a single example, being the title and code block let write_example = |w: &mut W, (path, call_data): (&PathBuf, &CallData)| -> bool { let contents = match fs::read_to_string(path) { Ok(contents) => contents, Err(err) => { let span = item.span(tcx).map_or(DUMMY_SP, |span| span.inner()); tcx.dcx().span_err(span, format!("failed to read file {}: {err}", path.display())); return false; } }; // To reduce file sizes, we only want to embed the source code needed to understand the example, not // the entire file. So we find the smallest byte range that covers all items enclosing examples. assert!(!call_data.locations.is_empty()); let min_loc = call_data.locations.iter().min_by_key(|loc| loc.enclosing_item.byte_span.0).unwrap(); let byte_min = min_loc.enclosing_item.byte_span.0; let line_min = min_loc.enclosing_item.line_span.0; let max_loc = call_data.locations.iter().max_by_key(|loc| loc.enclosing_item.byte_span.1).unwrap(); let byte_max = max_loc.enclosing_item.byte_span.1; let line_max = max_loc.enclosing_item.line_span.1; // The output code is limited to that byte range. let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)]; // The call locations need to be updated to reflect that the size of the program has changed. // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point. let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data .locations .iter() .map(|loc| { let (byte_lo, byte_hi) = loc.call_ident.byte_span; let (line_lo, line_hi) = loc.call_expr.line_span; let byte_range = (byte_lo - byte_min, byte_hi - byte_min); let line_range = (line_lo - line_min, line_hi - line_min); let (line_url, line_title) = link_to_loc(call_data, loc); (byte_range, (line_range, line_url, line_title)) }) .unzip(); let (_, init_url, init_title) = &line_ranges[0]; let needs_expansion = line_max - line_min > NUM_VISIBLE_LINES; let locations_encoded = serde_json::to_string(&line_ranges).unwrap(); let source_map = tcx.sess.source_map(); let files = source_map.files(); let local = tcx.sess.local_crate_source_file().unwrap(); let get_file_start_pos = || { let crate_src = local.clone().into_local_path()?; let abs_crate_src = crate_src.canonicalize().ok()?; let crate_root = abs_crate_src.parent()?.parent()?; let rel_path = path.strip_prefix(crate_root).ok()?; files .iter() .find(|file| match &file.name { FileName::Real(RealFileName::LocalPath(other_path)) => rel_path == other_path, _ => false, }) .map(|file| file.start_pos) }; // Look for the example file in the source map if it exists, otherwise // return a span to the local crate's source file let Some(file_span) = get_file_start_pos() .or_else(|| { files .iter() .find(|file| match &file.name { FileName::Real(file_name) => file_name == &local, _ => false, }) .map(|file| file.start_pos) }) .map(|start_pos| { rustc_span::Span::with_root_ctxt( start_pos + BytePos(byte_min), start_pos + BytePos(byte_max), ) }) else { // if the fallback span can't be built, don't render the code for this example return false; }; let mut decoration_info = FxIndexMap::default(); decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]); decoration_info.insert("highlight", byte_ranges); sources::print_src( w, contents_subset, file_span, cx, &cx.root_path(), &highlight::DecorationInfo(decoration_info), &sources::SourceContext::Embedded(sources::ScrapedInfo { needs_expansion, offset: line_min, name: &call_data.display_name, url: init_url, title: init_title, locations: locations_encoded, }), ) .unwrap(); true }; // The call locations are output in sequence, so that sequence needs to be determined. // Ideally the most "relevant" examples would be shown first, but there's no general algorithm // for determining relevance. We instead proxy relevance with the following heuristics: // 1. Code written to be an example is better than code not written to be an example, e.g. // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo // directory structure in Rustdoc, so we proxy this by prioritizing code that comes from // a --crate-type bin. // 2. Smaller examples are better than large examples. So we prioritize snippets that have // the smallest number of lines in their enclosing item. // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the // ordering of examples from randomly changing between Rustdoc invocations. let ordered_locations = { fn sort_criterion<'a>( (_, call_data): &(&PathBuf, &'a CallData), ) -> (bool, u32, &'a String) { // Use the first location because that's what the user will see initially let (lo, hi) = call_data.locations[0].enclosing_item.byte_span; (!call_data.is_bin, hi - lo, &call_data.display_name) } let mut locs = call_locations.iter().collect::>(); locs.sort_by_key(sort_criterion); locs }; let mut it = ordered_locations.into_iter().peekable(); // An example may fail to write if its source can't be read for some reason, so this method // continues iterating until a write succeeds let write_and_skip_failure = |w: &mut W, it: &mut Peekable<_>| { for example in it.by_ref() { if write_example(&mut *w, example) { break; } } }; // Write just one example that's visible by default in the method's description. write_and_skip_failure(&mut w, &mut it); // Then add the remaining examples in a hidden section. if it.peek().is_some() { write!( w, "
\ \ More examples\ \
Hide additional examples
\
\
" )?; // Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could // make the page arbitrarily huge! for _ in 0..MAX_FULL_EXAMPLES { write_and_skip_failure(&mut w, &mut it); } // For the remaining examples, generate a
    containing links to the source files. if it.peek().is_some() { w.write_str( r#"")?; } w.write_str("
")?; } w.write_str("
") }