//! 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. //! //! 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. crate mod cache; #[cfg(test)] mod tests; mod context; mod print_item; mod write_shared; crate use context::*; use std::collections::VecDeque; use std::default::Default; use std::fmt; use std::path::PathBuf; use std::str; use std::string::ToString; use rustc_ast_pretty::pprust; use rustc_attr::{Deprecation, StabilityLevel}; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; use rustc_hir::Mutability; use rustc_middle::middle::stability; use rustc_span::symbol::{kw, sym, Symbol}; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use crate::clean::{self, FakeDefId, GetDefId, RenderedLink, SelfTy}; use crate::docfs::PathError; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::formats::{AssocItemRender, Impl, RenderMode}; use crate::html::escape::Escape; use crate::html::format::{ href, print_abi_with_space, print_default_space, print_generic_bounds, print_where_clause, Buffer, PrintWithSpace, }; use crate::html::markdown::{Markdown, MarkdownHtml, MarkdownSummaryLine}; /// A pair of name and its optional document. crate type NameDoc = (String, Option<String>); crate fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { crate::html::format::display_fn(move |f| { if !v.ends_with('/') && !v.is_empty() { write!(f, "{}/", v) } else { f.write_str(v) } }) } // 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)] crate struct IndexItem { crate ty: ItemType, crate name: String, crate path: String, crate desc: String, crate parent: Option<DefId>, crate parent_idx: Option<usize>, crate search_type: Option<IndexItemFunctionType>, crate aliases: Box<[String]>, } /// A type used for the search index. #[derive(Debug)] crate struct RenderType { ty: Option<DefId>, idx: Option<usize>, name: Option<String>, generics: Option<Vec<Generic>>, } impl Serialize for RenderType { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { if let Some(name) = &self.name { let mut seq = serializer.serialize_seq(None)?; if let Some(id) = self.idx { seq.serialize_element(&id)?; } else { seq.serialize_element(&name)?; } if let Some(generics) = &self.generics { seq.serialize_element(&generics)?; } seq.end() } else { serializer.serialize_none() } } } /// A type used for the search index. #[derive(Debug)] crate struct Generic { name: String, defid: Option<DefId>, idx: Option<usize>, } impl Serialize for Generic { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { if let Some(id) = self.idx { serializer.serialize_some(&id) } else { serializer.serialize_some(&self.name) } } } /// Full type of functions/methods in the search index. #[derive(Debug)] crate struct IndexItemFunctionType { inputs: Vec<TypeWithKind>, output: Option<Vec<TypeWithKind>>, } impl Serialize for IndexItemFunctionType { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { // If we couldn't figure out a type, just write `null`. let mut iter = self.inputs.iter(); if match self.output { Some(ref output) => iter.chain(output.iter()).any(|ref i| i.ty.name.is_none()), None => iter.any(|ref i| i.ty.name.is_none()), } { serializer.serialize_none() } else { let mut seq = serializer.serialize_seq(None)?; seq.serialize_element(&self.inputs)?; if let Some(output) = &self.output { if output.len() > 1 { seq.serialize_element(&output)?; } else { seq.serialize_element(&output[0])?; } } seq.end() } } } #[derive(Debug)] crate struct TypeWithKind { ty: RenderType, kind: ItemType, } impl From<(RenderType, ItemType)> for TypeWithKind { fn from(x: (RenderType, ItemType)) -> TypeWithKind { TypeWithKind { ty: x.0, kind: x.1 } } } impl Serialize for TypeWithKind { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { (&self.ty.name, self.kind).serialize(serializer) } } #[derive(Debug, Clone)] crate struct StylePath { /// The path to the theme crate path: PathBuf, /// What the `disabled` attribute should be set to in the HTML tag crate disabled: bool, } fn write_srclink(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { if let Some(l) = cx.src_href(item) { write!(buf, "<a class=\"srclink\" href=\"{}\" title=\"goto source code\">[src]</a>", l) } } #[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 { crate fn print(&self) -> impl fmt::Display + '_ { crate::html::format::display_fn(move |f| { write!(f, "<a href=\"{}\">{}</a>", 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: FxHashSet<ItemEntry>, enums: FxHashSet<ItemEntry>, unions: FxHashSet<ItemEntry>, primitives: FxHashSet<ItemEntry>, traits: FxHashSet<ItemEntry>, macros: FxHashSet<ItemEntry>, functions: FxHashSet<ItemEntry>, typedefs: FxHashSet<ItemEntry>, opaque_tys: FxHashSet<ItemEntry>, statics: FxHashSet<ItemEntry>, constants: FxHashSet<ItemEntry>, keywords: FxHashSet<ItemEntry>, attributes: FxHashSet<ItemEntry>, derives: FxHashSet<ItemEntry>, trait_aliases: FxHashSet<ItemEntry>, } impl AllTypes { fn new() -> AllTypes { let new_set = |cap| FxHashSet::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), typedefs: new_set(100), opaque_tys: new_set(100), statics: new_set(100), constants: new_set(100), keywords: new_set(100), attributes: new_set(100), derives: 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!("{}/{}.{}.html", url.join("/"), item_type, name); 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::Typedef => self.typedefs.insert(ItemEntry::new(new_url, name)), ItemType::OpaqueTy => self.opaque_tys.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.attributes.insert(ItemEntry::new(new_url, name)), ItemType::ProcDerive => self.derives.insert(ItemEntry::new(new_url, name)), ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), _ => true, }; } } } impl AllTypes { fn print(self, f: &mut Buffer) { fn print_entries(f: &mut Buffer, e: &FxHashSet<ItemEntry>, title: &str, class: &str) { if !e.is_empty() { let mut e: Vec<&ItemEntry> = e.iter().collect(); e.sort(); write!( f, "<h3 id=\"{}\">{}</h3><ul class=\"{} docblock\">", title.replace(' ', "-"), // IDs cannot contain whitespaces. title, class ); for s in e.iter() { write!(f, "<li>{}</li>", s.print()); } f.write_str("</ul>"); } } f.write_str( "<h1 class=\"fqn\">\ <span class=\"in-band\">List of all items</span>\ <span class=\"out-of-band\">\ <span id=\"render-detail\">\ <a id=\"toggle-all-docs\" href=\"javascript:void(0)\" \ title=\"collapse all docs\">\ [<span class=\"inner\">−</span>]\ </a>\ </span> </span> </h1>", ); // Note: print_entries does not escape the title, because we know the current set of titles // doesn't require escaping. print_entries(f, &self.structs, "Structs", "structs"); print_entries(f, &self.enums, "Enums", "enums"); print_entries(f, &self.unions, "Unions", "unions"); print_entries(f, &self.primitives, "Primitives", "primitives"); print_entries(f, &self.traits, "Traits", "traits"); print_entries(f, &self.macros, "Macros", "macros"); print_entries(f, &self.attributes, "Attribute Macros", "attributes"); print_entries(f, &self.derives, "Derive Macros", "derives"); print_entries(f, &self.functions, "Functions", "functions"); print_entries(f, &self.typedefs, "Typedefs", "typedefs"); print_entries(f, &self.trait_aliases, "Trait Aliases", "trait-aliases"); print_entries(f, &self.opaque_tys, "Opaque Types", "opaque-types"); print_entries(f, &self.statics, "Statics", "statics"); print_entries(f, &self.constants, "Constants", "constants") } } #[derive(Debug)] enum Setting { Section { description: &'static str, sub_settings: Vec<Setting>, }, Toggle { js_data_name: &'static str, description: &'static str, default_value: bool, }, Select { js_data_name: &'static str, description: &'static str, default_value: &'static str, options: Vec<(String, String)>, }, } impl Setting { fn display(&self, root_path: &str, suffix: &str) -> String { match *self { Setting::Section { description, ref sub_settings } => format!( "<div class=\"setting-line\">\ <div class=\"title\">{}</div>\ <div class=\"sub-settings\">{}</div> </div>", description, sub_settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>() ), Setting::Toggle { js_data_name, description, default_value } => format!( "<div class=\"setting-line\">\ <label class=\"toggle\">\ <input type=\"checkbox\" id=\"{}\" {}>\ <span class=\"slider\"></span>\ </label>\ <div>{}</div>\ </div>", js_data_name, if default_value { " checked" } else { "" }, description, ), Setting::Select { js_data_name, description, default_value, ref options } => format!( "<div class=\"setting-line\">\ <div>{}</div>\ <label class=\"select-wrapper\">\ <select id=\"{}\" autocomplete=\"off\">{}</select>\ <img src=\"{}down-arrow{}.svg\" alt=\"Select item\">\ </label>\ </div>", description, js_data_name, options .iter() .map(|opt| format!( "<option value=\"{}\" {}>{}</option>", opt.0, if opt.0 == default_value { "selected" } else { "" }, opt.1, )) .collect::<String>(), root_path, suffix, ), } } } impl From<(&'static str, &'static str, bool)> for Setting { fn from(values: (&'static str, &'static str, bool)) -> Setting { Setting::Toggle { js_data_name: values.0, description: values.1, default_value: values.2 } } } impl<T: Into<Setting>> From<(&'static str, Vec<T>)> for Setting { fn from(values: (&'static str, Vec<T>)) -> Setting { Setting::Section { description: values.0, sub_settings: values.1.into_iter().map(|v| v.into()).collect::<Vec<_>>(), } } } fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result<String, Error> { let theme_names: Vec<(String, String)> = themes .iter() .map(|entry| { let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path) .to_string(); Ok((theme.clone(), theme)) }) .collect::<Result<_, Error>>()?; // (id, explanation, default value) let settings: &[Setting] = &[ ( "Theme preferences", vec![ Setting::from(("use-system-theme", "Use system theme", true)), Setting::Select { js_data_name: "preferred-dark-theme", description: "Preferred dark theme", default_value: "dark", options: theme_names.clone(), }, Setting::Select { js_data_name: "preferred-light-theme", description: "Preferred light theme", default_value: "light", options: theme_names, }, ], ) .into(), ("auto-hide-large-items", "Auto-hide item contents for large items.", true).into(), ("auto-hide-method-docs", "Auto-hide item methods' documentation", false).into(), ("auto-hide-trait-implementations", "Auto-hide trait implementation documentation", false) .into(), ("auto-collapse-implementors", "Auto-hide implementors of a trait", true).into(), ("go-to-only-result", "Directly go to item in search if there is only one result", false) .into(), ("line-numbers", "Show line numbers on code examples", false).into(), ("disable-shortcuts", "Disable keyboard shortcuts", false).into(), ]; Ok(format!( "<h1 class=\"fqn\">\ <span class=\"in-band\">Rustdoc settings</span>\ </h1>\ <div class=\"settings\">{}</div>\ <script src=\"{}settings{}.js\"></script>", settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>(), root_path, suffix )) } fn document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) { if let Some(ref name) = item.name { info!("Documenting {}", name); } document_item_info(w, cx, item, parent); if parent.is_none() { document_full_collapsible(w, item, cx); } else { document_full(w, item, cx); } } /// Render md_text as markdown. fn render_markdown(w: &mut Buffer, cx: &Context<'_>, md_text: &str, links: Vec<RenderedLink>) { let mut ids = cx.id_map.borrow_mut(); write!( w, "<div class=\"docblock\">{}</div>", Markdown( md_text, &links, &mut ids, cx.shared.codes, cx.shared.edition(), &cx.shared.playground ) .into_string() ) } /// 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( w: &mut Buffer, item: &clean::Item, cx: &Context<'_>, link: AssocItemLink<'_>, parent: &clean::Item, show_def_docs: bool, ) { document_item_info(w, cx, item, Some(parent)); if !show_def_docs { return; } if let Some(s) = item.doc_value() { let mut summary_html = MarkdownSummaryLine(&s, &item.links(cx)).into_string(); if s.contains('\n') { let link = format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link, cx)); if let Some(idx) = summary_html.rfind("</p>") { summary_html.insert_str(idx, &link); } else { summary_html.push_str(&link); } } write!(w, "<div class='docblock'>{}</div>", summary_html,); } } fn document_full_collapsible(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>) { document_full_inner(w, item, cx, true); } fn document_full(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>) { document_full_inner(w, item, cx, false); } fn document_full_inner(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>, is_collapsible: bool) { if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) { debug!("Doc block: =====\n{}\n=====", s); if is_collapsible { w.write_str( "<details class=\"rustdoc-toggle top-doc\" open>\ <summary class=\"hideme\">\ <span>Expand description</span>\ </summary>", ); render_markdown(w, cx, &s, item.links(cx)); w.write_str("</details>"); } else { render_markdown(w, cx, &s, item.links(cx)); } } } /// Add extra information about an item such as: /// /// * Stability /// * Deprecated /// * Required features (through the `doc_cfg` feature) fn document_item_info( w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>, ) { let item_infos = short_item_info(item, cx, parent); if !item_infos.is_empty() { w.write_str("<div class=\"item-info\">"); for info in item_infos { w.write_str(&info); } w.write_str("</div>"); } } fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> { 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 {:?} - {:?} = {:?}", item.cfg, parent.and_then(|p| p.cfg.as_ref()), cfg); Some(format!("<div class=\"stab portability\">{}</div>", cfg?.render_long_html())) } /// 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<String> { let mut extra_info = vec![]; let error_codes = cx.shared.codes; if let Some(Deprecation { note, since, is_since_rustc_version, suggestion: _ }) = item.deprecation(cx.tcx()) { // We display deprecation messages for #[deprecated] and #[rustc_deprecated] // but only display the future-deprecation messages for #[rustc_deprecated]. let mut message = if let Some(since) = since { let since = &since.as_str(); if !stability::deprecation_in_effect(is_since_rustc_version, Some(since)) { if *since == "TBD" { String::from("Deprecating in a future Rust version") } else { format!("Deprecating in {}", Escape(since)) } } else { format!("Deprecated since {}", Escape(since)) } } else { String::from("Deprecated") }; if let Some(note) = note { let note = note.as_str(); let mut ids = cx.id_map.borrow_mut(); let html = MarkdownHtml( ¬e, &mut ids, error_codes, cx.shared.edition(), &cx.shared.playground, ); message.push_str(&format!(": {}", html.into_string())); } extra_info.push(format!( "<div class=\"stab deprecated\"><span class=\"emoji\">👎</span> {}</div>", 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 mut message = "<span class=\"emoji\">🔬</span> This is a nightly-only experimental API.".to_owned(); let mut feature = format!("<code>{}</code>", Escape(&feature.as_str())); if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) { feature.push_str(&format!( " <a href=\"{url}{issue}\">#{issue}</a>", url = url, issue = issue )); } message.push_str(&format!(" ({})", feature)); if let Some(unstable_reason) = reason { let mut ids = cx.id_map.borrow_mut(); message = format!( "<details><summary>{}</summary>{}</details>", message, MarkdownHtml( &unstable_reason.as_str(), &mut ids, error_codes, cx.shared.edition(), &cx.shared.playground, ) .into_string() ); } extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message)); } if let Some(portability) = portability(item, parent) { extra_info.push(portability); } extra_info } fn render_impls( cx: &Context<'_>, w: &mut Buffer, traits: &[&&Impl], containing_item: &clean::Item, ) { let cache = cx.cache(); let tcx = cx.tcx(); let mut impls = traits .iter() .map(|i| { let did = i.trait_did_full(cache).unwrap(); let provided_trait_methods = i.inner_impl().provided_trait_methods(tcx); let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods); let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() }; render_impl( &mut buffer, cx, i, containing_item, assoc_link, RenderMode::Normal, containing_item.stable_since(tcx).as_deref(), containing_item.const_stable_since(tcx).as_deref(), true, None, false, true, &[], ); buffer.into_inner() }) .collect::<Vec<_>>(); impls.sort(); w.write_str(&impls.join("")); } fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String { use crate::formats::item_type::ItemType::*; let name = it.name.as_ref().unwrap(); let ty = match it.type_() { Typedef | AssocType => AssocType, s => s, }; let anchor = format!("#{}.{}", ty, name); match link { AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), AssocItemLink::Anchor(None) => anchor, AssocItemLink::GotoSource(did, _) => { href(did.expect_real(), cx).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor) } } } fn assoc_const( w: &mut Buffer, it: &clean::Item, ty: &clean::Type, _default: Option<&String>, link: AssocItemLink<'_>, extra: &str, cx: &Context<'_>, ) { write!( w, "{}{}const <a href=\"{}\" class=\"constant\"><b>{}</b></a>: {}", extra, it.visibility.print_with_space(it.def_id, cx), naive_assoc_href(it, link, cx), it.name.as_ref().unwrap(), ty.print(cx) ); } fn assoc_type( w: &mut Buffer, it: &clean::Item, bounds: &[clean::GenericBound], default: Option<&clean::Type>, link: AssocItemLink<'_>, extra: &str, cx: &Context<'_>, ) { write!( w, "{}type <a href=\"{}\" class=\"type\">{}</a>", extra, naive_assoc_href(it, link, cx), it.name.as_ref().unwrap() ); if !bounds.is_empty() { write!(w, ": {}", print_generic_bounds(bounds, cx)) } if let Some(default) = default { write!(w, " = {}", default.print(cx)) } } fn render_stability_since_raw( w: &mut Buffer, ver: Option<&str>, const_ver: Option<&str>, containing_ver: Option<&str>, containing_const_ver: Option<&str>, ) { let ver = ver.filter(|inner| !inner.is_empty()); let const_ver = const_ver.filter(|inner| !inner.is_empty()); match (ver, const_ver) { (Some(v), Some(cv)) if const_ver != containing_const_ver => { write!( w, "<span class=\"since\" title=\"Stable since Rust version {0}, const since {1}\">{0} (const: {1})</span>", v, cv ); } (Some(v), _) if ver != containing_ver => { write!( w, "<span class=\"since\" title=\"Stable since Rust version {0}\">{0}</span>", v ); } _ => {} } } fn render_assoc_item( w: &mut Buffer, item: &clean::Item, link: AssocItemLink<'_>, parent: ItemType, cx: &Context<'_>, ) { fn method( w: &mut Buffer, meth: &clean::Item, header: hir::FnHeader, g: &clean::Generics, d: &clean::FnDecl, link: AssocItemLink<'_>, parent: ItemType, cx: &Context<'_>, ) { let name = meth.name.as_ref().unwrap(); let href = match link { AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), AssocItemLink::Anchor(None) => format!("#{}.{}", meth.type_(), name), AssocItemLink::GotoSource(did, provided_methods) => { // We're creating a link from an impl-item to the corresponding // trait-item and need to map the anchored type accordingly. let ty = if provided_methods.contains(&name) { ItemType::Method } else { ItemType::TyMethod }; href(did.expect_real(), cx) .map(|p| format!("{}#{}.{}", p.0, ty, name)) .unwrap_or_else(|| format!("#{}.{}", ty, name)) } }; let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string(); let constness = header.constness.print_with_space(); let asyncness = header.asyncness.print_with_space(); let unsafety = header.unsafety.print_with_space(); let defaultness = print_default_space(meth.is_default()); let abi = print_abi_with_space(header.abi).to_string(); // 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() + constness.len() + asyncness.len() + unsafety.len() + defaultness.len() + abi.len() + name.as_str().len() + generics_len; let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; let indent_str = " "; render_attributes_in_pre(w, meth, indent_str); (4, indent_str, false) } else { render_attributes_in_code(w, meth); (0, "", true) }; w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len()); write!( w, "{}{}{}{}{}{}{}fn <a href=\"{href}\" class=\"fnname\">{name}</a>\ {generics}{decl}{notable_traits}{where_clause}", indent_str, vis, constness, asyncness, unsafety, defaultness, abi, href = href, name = name, generics = g.print(cx), decl = d.full_print(header_len, indent, header.asyncness, cx), notable_traits = notable_traits_decl(&d, cx), where_clause = print_where_clause(g, cx, indent, end_newline), ) } match *item.kind { clean::StrippedItem(..) => {} clean::TyMethodItem(ref m) => { method(w, item, m.header, &m.generics, &m.decl, link, parent, cx) } clean::MethodItem(ref m, _) => { method(w, item, m.header, &m.generics, &m.decl, link, parent, cx) } clean::AssocConstItem(ref ty, ref default) => assoc_const( w, item, ty, default.as_ref(), link, if parent == ItemType::Trait { " " } else { "" }, cx, ), clean::AssocTypeItem(ref bounds, ref default) => assoc_type( w, item, bounds, default.as_ref(), link, if parent == ItemType::Trait { " " } else { "" }, cx, ), _ => panic!("render_assoc_item called on non-associated-item"), } } const ALLOWED_ATTRIBUTES: &[Symbol] = &[sym::export_name, sym::link_section, sym::no_mangle, sym::repr, sym::non_exhaustive]; fn attributes(it: &clean::Item) -> Vec<String> { it.attrs .other_attrs .iter() .filter_map(|attr| { if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) { Some(pprust::attribute_to_string(&attr).replace("\n", "").replace(" ", " ")) } else { None } }) .collect() } // When an attribute is rendered inside a `<pre>` tag, it is formatted using // a whitespace prefix and newline. fn render_attributes_in_pre(w: &mut Buffer, it: &clean::Item, prefix: &str) { for a in attributes(it) { writeln!(w, "{}{}", prefix, a); } } // When an attribute is rendered inside a <code> tag, it is formatted using // a div to produce a newline after it. fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item) { for a in attributes(it) { write!(w, "<div class=\"code-attribute\">{}</div>", a); } } #[derive(Copy, Clone)] enum AssocItemLink<'a> { Anchor(Option<&'a str>), GotoSource(FakeDefId, &'a FxHashSet<Symbol>), } impl<'a> AssocItemLink<'a> { fn anchor(&self, id: &'a str) -> Self { match *self { AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(&id)), ref other => *other, } } } fn render_assoc_items( w: &mut Buffer, cx: &Context<'_>, containing_item: &clean::Item, it: DefId, what: AssocItemRender<'_>, ) { info!("Documenting associated items of {:?}", containing_item.name); let v = match cx.cache.impls.get(&it) { Some(v) => v, None => return, }; let tcx = cx.tcx(); let cache = cx.cache(); let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none()); if !non_trait.is_empty() { let render_mode = match what { AssocItemRender::All => { w.write_str( "<h2 id=\"implementations\" class=\"small-section-header\">\ Implementations<a href=\"#implementations\" class=\"anchor\"></a>\ </h2>", ); RenderMode::Normal } AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => { write!( w, "<h2 id=\"deref-methods\" class=\"small-section-header\">\ Methods from {trait_}<Target = {type_}>\ <a href=\"#deref-methods\" class=\"anchor\"></a>\ </h2>", trait_ = trait_.print(cx), type_ = type_.print(cx), ); RenderMode::ForDeref { mut_: deref_mut_ } } }; for i in &non_trait { render_impl( w, cx, i, containing_item, AssocItemLink::Anchor(None), render_mode, containing_item.stable_since(tcx).as_deref(), containing_item.const_stable_since(tcx).as_deref(), true, None, false, true, &[], ); } } if let AssocItemRender::DerefFor { .. } = what { return; } if !traits.is_empty() { let deref_impl = traits .iter() .find(|t| t.inner_impl().trait_.def_id_full(cache) == cx.cache.deref_trait_did); if let Some(impl_) = deref_impl { let has_deref_mut = traits .iter() .any(|t| t.inner_impl().trait_.def_id_full(cache) == cx.cache.deref_mut_trait_did); render_deref_methods(w, cx, impl_, containing_item, has_deref_mut); } let (synthetic, concrete): (Vec<&&Impl>, Vec<&&Impl>) = traits.iter().partition(|t| t.inner_impl().synthetic); let (blanket_impl, concrete): (Vec<&&Impl>, _) = concrete.into_iter().partition(|t| t.inner_impl().blanket_impl.is_some()); let mut impls = Buffer::empty_from(&w); render_impls(cx, &mut impls, &concrete, containing_item); let impls = impls.into_inner(); if !impls.is_empty() { write!( w, "<h2 id=\"trait-implementations\" class=\"small-section-header\">\ Trait Implementations<a href=\"#trait-implementations\" class=\"anchor\"></a>\ </h2>\ <div id=\"trait-implementations-list\">{}</div>", impls ); } if !synthetic.is_empty() { w.write_str( "<h2 id=\"synthetic-implementations\" class=\"small-section-header\">\ Auto Trait Implementations\ <a href=\"#synthetic-implementations\" class=\"anchor\"></a>\ </h2>\ <div id=\"synthetic-implementations-list\">", ); render_impls(cx, w, &synthetic, containing_item); w.write_str("</div>"); } if !blanket_impl.is_empty() { w.write_str( "<h2 id=\"blanket-implementations\" class=\"small-section-header\">\ Blanket Implementations\ <a href=\"#blanket-implementations\" class=\"anchor\"></a>\ </h2>\ <div id=\"blanket-implementations-list\">", ); render_impls(cx, w, &blanket_impl, containing_item); w.write_str("</div>"); } } } fn render_deref_methods( w: &mut Buffer, cx: &Context<'_>, impl_: &Impl, container_item: &clean::Item, deref_mut: bool, ) { 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::TypedefItem(ref t, true) => Some(match *t { clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), _ => (&t.type_, &t.type_), }), _ => None, }) .expect("Expected associated type binding"); debug!("Render deref methods for {:#?}, target {:#?}", impl_.inner_impl().for_, target); let what = AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut }; if let Some(did) = target.def_id_full(cx.cache()) { if let Some(type_did) = impl_.inner_impl().for_.def_id_full(cx.cache()) { // `impl Deref<Target = S> for S` if did == type_did { // Avoid infinite cycles return; } } render_assoc_items(w, cx, container_item, did, what); } else { if let Some(prim) = target.primitive_type() { if let Some(&did) = cx.cache.primitive_locations.get(&prim) { render_assoc_items(w, cx, container_item, did, what); } } } } fn should_render_item(item: &clean::Item, deref_mut_: bool, cache: &Cache) -> bool { let self_type_opt = match *item.kind { clean::MethodItem(ref method, _) => method.decl.self_type(), clean::TyMethodItem(ref method) => method.decl.self_type(), _ => None, }; if let Some(self_ty) = self_type_opt { let (by_mut_ref, by_box, by_value) = match self_ty { SelfTy::SelfBorrowed(_, mutability) | SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => { (mutability == Mutability::Mut, false, false) } SelfTy::SelfExplicit(clean::ResolvedPath { did, .. }) => { (false, Some(did) == cache.owned_box_did, false) } SelfTy::SelfValue => (false, false, true), _ => (false, false, false), }; (deref_mut_ || !by_mut_ref) && !by_box && !by_value } else { false } } fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String { let mut out = Buffer::html(); let mut trait_ = String::new(); if let Some(did) = decl.output.def_id_full(cx.cache()) { if let Some(impls) = cx.cache().impls.get(&did) { for i in impls { let impl_ = i.inner_impl(); if impl_.trait_.def_id().map_or(false, |d| { cx.cache().traits.get(&d).map(|t| t.is_notable).unwrap_or(false) }) { if out.is_empty() { write!( &mut out, "<h3 class=\"notable\">Notable traits for {}</h3>\ <code class=\"content\">", impl_.for_.print(cx) ); trait_.push_str(&impl_.for_.print(cx).to_string()); } //use the "where" class here to make it small write!( &mut out, "<span class=\"where fmt-newline\">{}</span>", impl_.print(false, cx) ); let t_did = impl_.trait_.def_id_full(cx.cache()).unwrap(); for it in &impl_.items { if let clean::TypedefItem(ref tydef, _) = *it.kind { out.push_str("<span class=\"where fmt-newline\"> "); assoc_type( &mut out, it, &[], Some(&tydef.type_), AssocItemLink::GotoSource(t_did.into(), &FxHashSet::default()), "", cx, ); out.push_str(";</span>"); } } } } } } if !out.is_empty() { out.insert_str( 0, "<span class=\"notable-traits\"><span class=\"notable-traits-tooltip\">ⓘ\ <div class=\"notable-traits-tooltiptext\"><span class=\"docblock\">", ); out.push_str("</code></span></div></span></span>"); } out.into_inner() } fn render_impl( w: &mut Buffer, cx: &Context<'_>, i: &Impl, parent: &clean::Item, link: AssocItemLink<'_>, render_mode: RenderMode, outer_version: Option<&str>, outer_const_version: Option<&str>, show_def_docs: bool, use_absolute: Option<bool>, is_on_foreign_type: bool, show_default_items: bool, // 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], ) { let cache = cx.cache(); let traits = &cache.traits; let trait_ = i.trait_did_full(cache).map(|did| &traits[&did]); let mut close_tags = String::new(); // 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. fn doc_impl_item( boring: &mut Buffer, interesting: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: &clean::Item, link: AssocItemLink<'_>, render_mode: RenderMode, is_default_item: bool, outer_version: Option<&str>, outer_const_version: Option<&str>, trait_: Option<&clean::Trait>, show_def_docs: bool, ) { let item_type = item.type_(); let name = item.name.as_ref().unwrap(); let tcx = cx.tcx(); let render_method_item = match render_mode { RenderMode::Normal => true, RenderMode::ForDeref { mut_: deref_mut_ } => { should_render_item(&item, deref_mut_, &cx.cache) } }; let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" }; let mut doc_buffer = Buffer::empty_from(boring); let mut info_buffer = Buffer::empty_from(boring); 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_some() { document_item_info(&mut info_buffer, cx, it, Some(parent)); document_full(&mut doc_buffer, item, cx); short_documented = false; } else { // In case the item isn't documented, // provide short documentation from the trait. document_short(&mut doc_buffer, it, cx, link, parent, show_def_docs); } } } else { document_item_info(&mut info_buffer, cx, item, Some(parent)); if show_def_docs { document_full(&mut doc_buffer, item, cx); short_documented = false; } } } else { document_short(&mut doc_buffer, item, cx, link, parent, show_def_docs); } } let w = if short_documented && trait_.is_some() { interesting } else { boring }; let toggled = !doc_buffer.is_empty(); if toggled { let method_toggle_class = if item_type == ItemType::Method { " method-toggle" } else { "" }; write!(w, "<details class=\"rustdoc-toggle{}\" open><summary>", method_toggle_class); } match *item.kind { clean::MethodItem(..) | clean::TyMethodItem(_) => { // 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.as_str().eq(&name.as_str())).unwrap_or(false) }) }) .map(|item| format!("{}.{}", item.type_(), name)); write!( w, "<div id=\"{}\" class=\"{}{} has-srclink\">", id, item_type, in_trait_class, ); w.write_str("<code>"); render_assoc_item( w, item, link.anchor(source_id.as_ref().unwrap_or(&id)), ItemType::Impl, cx, ); w.write_str("</code>"); render_stability_since_raw( w, item.stable_since(tcx).as_deref(), item.const_stable_since(tcx).as_deref(), outer_version, outer_const_version, ); write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); write_srclink(cx, item, w); w.write_str("</div>"); } } clean::TypedefItem(ref tydef, _) => { let source_id = format!("{}.{}", ItemType::AssocType, name); let id = cx.derive_id(source_id.clone()); write!( w, "<div id=\"{}\" class=\"{}{} has-srclink\"><code>", id, item_type, in_trait_class ); assoc_type( w, item, &Vec::new(), Some(&tydef.type_), link.anchor(if trait_.is_some() { &source_id } else { &id }), "", cx, ); w.write_str("</code>"); write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); w.write_str("</div>"); } clean::AssocConstItem(ref ty, ref default) => { let source_id = format!("{}.{}", item_type, name); let id = cx.derive_id(source_id.clone()); write!( w, "<div id=\"{}\" class=\"{}{} has-srclink\"><code>", id, item_type, in_trait_class ); assoc_const( w, item, ty, default.as_ref(), link.anchor(if trait_.is_some() { &source_id } else { &id }), "", cx, ); w.write_str("</code>"); render_stability_since_raw( w, item.stable_since(tcx).as_deref(), item.const_stable_since(tcx).as_deref(), outer_version, outer_const_version, ); write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); write_srclink(cx, item, w); w.write_str("</div>"); } clean::AssocTypeItem(ref bounds, ref default) => { let source_id = format!("{}.{}", item_type, name); let id = cx.derive_id(source_id.clone()); write!(w, "<div id=\"{}\" class=\"{}{}\"><code>", id, item_type, in_trait_class,); assoc_type( w, item, bounds, default.as_ref(), link.anchor(if trait_.is_some() { &source_id } else { &id }), "", cx, ); w.write_str("</code>"); write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); w.write_str("</div>"); } clean::StrippedItem(..) => return, _ => panic!("can't make docs for trait item with name {:?}", item.name), } w.push_buffer(info_buffer); if toggled { w.write_str("</summary>"); w.push_buffer(doc_buffer); w.push_str("</details>"); } } let mut impl_items = Buffer::empty_from(w); let mut default_impl_items = Buffer::empty_from(w); for trait_item in &i.inner_impl().items { 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, outer_version, outer_const_version, trait_.map(|t| &t.trait_), show_def_docs, ); } fn render_default_items( boring: &mut Buffer, interesting: &mut Buffer, cx: &Context<'_>, t: &clean::Trait, i: &clean::Impl, parent: &clean::Item, render_mode: RenderMode, outer_version: Option<&str>, outer_const_version: Option<&str>, show_def_docs: bool, ) { for trait_item in &t.items { let n = trait_item.name; if i.items.iter().any(|m| m.name == n) { continue; } let did = i.trait_.as_ref().unwrap().def_id_full(cx.cache()).unwrap(); let provided_methods = i.provided_trait_methods(cx.tcx()); let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods); doc_impl_item( boring, interesting, cx, trait_item, parent, assoc_link, render_mode, true, outer_version, outer_const_version, Some(t), show_def_docs, ); } } // 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 show_default_items { if let Some(t) = trait_ { render_default_items( &mut default_impl_items, &mut impl_items, cx, &t.trait_, &i.inner_impl(), &i.impl_item, render_mode, outer_version, outer_const_version, show_def_docs, ); } } if render_mode == RenderMode::Normal { let toggled = !impl_items.is_empty() || !default_impl_items.is_empty(); if toggled { close_tags.insert_str(0, "</details>"); write!(w, "<details class=\"rustdoc-toggle implementors-toggle\" open>"); } if toggled { write!(w, "<summary>") } render_impl_summary( w, cx, i, outer_version, outer_const_version, show_def_docs, use_absolute, is_on_foreign_type, aliases, ); if toggled { write!(w, "</summary>") } if trait_.is_some() { if let Some(portability) = portability(&i.impl_item, Some(parent)) { write!(w, "<div class=\"item-info\">{}</div>", portability); } } if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) { let mut ids = cx.id_map.borrow_mut(); write!( w, "<div class=\"docblock\">{}</div>", Markdown( &*dox, &i.impl_item.links(cx), &mut ids, cx.shared.codes, cx.shared.edition(), &cx.shared.playground ) .into_string() ); } } if !default_impl_items.is_empty() || !impl_items.is_empty() { w.write_str("<div class=\"impl-items\">"); w.push_buffer(default_impl_items); w.push_buffer(impl_items); close_tags.insert_str(0, "</div>"); } w.write_str(&close_tags); } fn render_impl_summary( w: &mut Buffer, cx: &Context<'_>, i: &Impl, outer_version: Option<&str>, outer_const_version: Option<&str>, show_def_docs: bool, use_absolute: Option<bool>, is_on_foreign_type: bool, // 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], ) { let tcx = cx.tcx(); let id = cx.derive_id(match i.inner_impl().trait_ { Some(ref t) => { if is_on_foreign_type { get_id_for_impl_on_foreign_type(&i.inner_impl().for_, t, cx) } else { format!("impl-{}", small_url_encode(format!("{:#}", t.print(cx)))) } } None => "impl".to_string(), }); let aliases = if aliases.is_empty() { String::new() } else { format!(" data-aliases=\"{}\"", aliases.join(",")) }; if let Some(use_absolute) = use_absolute { write!( w, "<div id=\"{}\" class=\"impl has-srclink\"{}>\ <code class=\"in-band\">", id, aliases ); write!(w, "{}", i.inner_impl().print(use_absolute, cx)); if show_def_docs { for it in &i.inner_impl().items { if let clean::TypedefItem(ref tydef, _) = *it.kind { w.write_str("<span class=\"where fmt-newline\"> "); assoc_type(w, it, &[], Some(&tydef.type_), AssocItemLink::Anchor(None), "", cx); w.write_str(";</span>"); } } } w.write_str("</code>"); } else { write!( w, "<div id=\"{}\" class=\"impl has-srclink\"{}>\ <code class=\"in-band\">{}</code>", id, aliases, i.inner_impl().print(false, cx) ); } write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); render_stability_since_raw( w, i.impl_item.stable_since(tcx).as_deref(), i.impl_item.const_stable_since(tcx).as_deref(), outer_version, outer_const_version, ); write_srclink(cx, &i.impl_item, w); w.write_str("</div>"); } fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { let parentlen = cx.current.len() - if it.is_mod() { 1 } else { 0 }; if it.is_struct() || it.is_trait() || it.is_primitive() || it.is_union() || it.is_enum() || it.is_mod() || it.is_typedef() { write!( buffer, "<p class=\"location\">{}{}</p>", match *it.kind { clean::StructItem(..) => "Struct ", clean::TraitItem(..) => "Trait ", clean::PrimitiveItem(..) => "Primitive Type ", clean::UnionItem(..) => "Union ", clean::EnumItem(..) => "Enum ", clean::TypedefItem(..) => "Type Definition ", clean::ForeignTypeItem => "Foreign Type ", clean::ModuleItem(..) => if it.is_crate() { "Crate " } else { "Module " }, _ => "", }, it.name.as_ref().unwrap() ); } if it.is_crate() { if let Some(ref version) = cx.cache.crate_version { write!( buffer, "<div class=\"block version\">\ <p>Version {}</p>\ </div>", Escape(version), ); } } buffer.write_str("<div class=\"sidebar-elems\">"); if it.is_crate() { write!( buffer, "<a id=\"all-types\" href=\"all.html\"><p>See all {}'s items</p></a>", it.name.as_ref().expect("crates always have a name"), ); } match *it.kind { clean::StructItem(ref s) => sidebar_struct(cx, buffer, it, s), clean::TraitItem(ref t) => sidebar_trait(cx, buffer, it, t), clean::PrimitiveItem(_) => sidebar_primitive(cx, buffer, it), clean::UnionItem(ref u) => sidebar_union(cx, buffer, it, u), clean::EnumItem(ref e) => sidebar_enum(cx, buffer, it, e), clean::TypedefItem(_, _) => sidebar_typedef(cx, buffer, it), clean::ModuleItem(ref m) => sidebar_module(buffer, &m.items), clean::ForeignTypeItem => sidebar_foreign_type(cx, buffer, it), _ => {} } // The sidebar is designed to display sibling functions, modules and // other miscellaneous information. since there are lots of sibling // items (and that causes quadratic growth in large modules), // we refactor common parts into a shared JavaScript file per module. // still, we don't move everything into JS because we want to preserve // as much HTML as possible in order to allow non-JS-enabled browsers // to navigate the documentation (though slightly inefficiently). if !it.is_mod() { buffer.write_str("<p class=\"location\">Other items in<br>"); for (i, name) in cx.current.iter().take(parentlen).enumerate() { if i > 0 { buffer.write_str("::<wbr>"); } write!( buffer, "<a href=\"{}index.html\">{}</a>", &cx.root_path()[..(cx.current.len() - i - 1) * 3], *name ); } buffer.write_str("</p>"); } // Sidebar refers to the enclosing module, not this module. let relpath = if it.is_mod() && parentlen != 0 { "./" } else { "" }; write!( buffer, "<div id=\"sidebar-vars\" data-name=\"{name}\" data-ty=\"{ty}\" data-relpath=\"{path}\">\ </div>", name = it.name.unwrap_or(kw::Empty), ty = it.type_(), path = relpath ); write!(buffer, "<script defer src=\"{}sidebar-items.js\"></script>", relpath); // Closes sidebar-elems div. buffer.write_str("</div>"); } fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String { if used_links.insert(url.clone()) { return url; } let mut add = 1; while !used_links.insert(format!("{}-{}", url, add)) { add += 1; } format!("{}-{}", url, add) } fn get_methods( i: &clean::Impl, for_deref: bool, used_links: &mut FxHashSet<String>, deref_mut: bool, cache: &Cache, ) -> Vec<String> { i.items .iter() .filter_map(|item| match item.name { Some(ref name) if !name.is_empty() && item.is_method() => { if !for_deref || should_render_item(item, deref_mut, cache) { Some(format!( "<a href=\"#{}\">{}</a>", get_next_url(used_links, format!("method.{}", name)), name )) } else { None } } _ => None, }) .collect::<Vec<_>>() } // The point is to url encode any potential character from a type with genericity. fn small_url_encode(s: String) -> String { let mut st = String::new(); let mut last_match = 0; for (idx, c) in s.char_indices() { let escaped = match c { '<' => "%3C", '>' => "%3E", ' ' => "%20", '?' => "%3F", '\'' => "%27", '&' => "%26", ',' => "%2C", ':' => "%3A", ';' => "%3B", '[' => "%5B", ']' => "%5D", '"' => "%22", _ => continue, }; st += &s[last_match..idx]; st += escaped; // NOTE: we only expect single byte characters here - which is fine as long as we // only match single byte characters last_match = idx + 1; } if last_match != 0 { st += &s[last_match..]; st } else { s } } fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { let did = it.def_id.expect_real(); if let Some(v) = cx.cache.impls.get(&did) { let mut used_links = FxHashSet::default(); let cache = cx.cache(); { let used_links_bor = &mut used_links; let mut ret = v .iter() .filter(|i| i.inner_impl().trait_.is_none()) .flat_map(move |i| { get_methods(i.inner_impl(), false, used_links_bor, false, &cx.cache) }) .collect::<Vec<_>>(); if !ret.is_empty() { // We want links' order to be reproducible so we don't use unstable sort. ret.sort(); out.push_str( "<a class=\"sidebar-title\" href=\"#implementations\">Methods</a>\ <div class=\"sidebar-links\">", ); for line in ret { out.push_str(&line); } out.push_str("</div>"); } } if v.iter().any(|i| i.inner_impl().trait_.is_some()) { let format_impls = |impls: Vec<&Impl>| { let mut links = FxHashSet::default(); let mut ret = impls .iter() .filter_map(|it| { if let Some(ref i) = it.inner_impl().trait_ { let i_display = format!("{:#}", i.print(cx)); let out = Escape(&i_display); let encoded = small_url_encode(format!("{:#}", i.print(cx))); let generated = format!( "<a href=\"#impl-{}\">{}{}</a>", encoded, if it.inner_impl().negative_polarity { "!" } else { "" }, out ); if links.insert(generated.clone()) { Some(generated) } else { None } } else { None } }) .collect::<Vec<String>>(); ret.sort(); ret }; let write_sidebar_links = |out: &mut Buffer, links: Vec<String>| { out.push_str("<div class=\"sidebar-links\">"); for link in links { out.push_str(&link); } out.push_str("</div>"); }; let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().synthetic); let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = concrete .into_iter() .partition::<Vec<_>, _>(|i| i.inner_impl().blanket_impl.is_some()); let concrete_format = format_impls(concrete); let synthetic_format = format_impls(synthetic); let blanket_format = format_impls(blanket_impl); if !concrete_format.is_empty() { out.push_str( "<a class=\"sidebar-title\" href=\"#trait-implementations\">\ Trait Implementations</a>", ); write_sidebar_links(out, concrete_format); } if !synthetic_format.is_empty() { out.push_str( "<a class=\"sidebar-title\" href=\"#synthetic-implementations\">\ Auto Trait Implementations</a>", ); write_sidebar_links(out, synthetic_format); } if !blanket_format.is_empty() { out.push_str( "<a class=\"sidebar-title\" href=\"#blanket-implementations\">\ Blanket Implementations</a>", ); write_sidebar_links(out, blanket_format); } if let Some(impl_) = v .iter() .filter(|i| i.inner_impl().trait_.is_some()) .find(|i| i.inner_impl().trait_.def_id_full(cache) == cx.cache.deref_trait_did) { sidebar_deref_methods(cx, out, impl_, v); } } } } fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &Vec<Impl>) { let c = cx.cache(); debug!("found Deref: {:?}", impl_); if let Some((target, real_target)) = impl_.inner_impl().items.iter().find_map(|item| match *item.kind { clean::TypedefItem(ref t, true) => Some(match *t { clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), _ => (&t.type_, &t.type_), }), _ => None, }) { debug!("found target, real_target: {:?} {:?}", target, real_target); if let Some(did) = target.def_id_full(c) { if let Some(type_did) = impl_.inner_impl().for_.def_id_full(c) { // `impl Deref<Target = S> for S` if did == type_did { // Avoid infinite cycles return; } } } let deref_mut = v .iter() .filter(|i| i.inner_impl().trait_.is_some()) .any(|i| i.inner_impl().trait_.def_id_full(c) == c.deref_mut_trait_did); let inner_impl = target .def_id_full(c) .or_else(|| { target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned()) }) .and_then(|did| c.impls.get(&did)); if let Some(impls) = inner_impl { debug!("found inner_impl: {:?}", impls); let mut used_links = FxHashSet::default(); let mut ret = impls .iter() .filter(|i| i.inner_impl().trait_.is_none()) .flat_map(|i| get_methods(i.inner_impl(), true, &mut used_links, deref_mut, c)) .collect::<Vec<_>>(); if !ret.is_empty() { write!( out, "<a class=\"sidebar-title\" href=\"#deref-methods\">Methods from {}<Target={}></a>", Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))), Escape(&format!("{:#}", real_target.print(cx))), ); // We want links' order to be reproducible so we don't use unstable sort. ret.sort(); out.push_str("<div class=\"sidebar-links\">"); for link in ret { out.push_str(&link); } out.push_str("</div>"); } } // Recurse into any further impls that might exist for `target` if let Some(target_did) = target.def_id_full(c) { if let Some(target_impls) = c.impls.get(&target_did) { if let Some(target_deref_impl) = target_impls .iter() .filter(|i| i.inner_impl().trait_.is_some()) .find(|i| i.inner_impl().trait_.def_id_full(c) == c.deref_trait_did) { sidebar_deref_methods(cx, out, target_deref_impl, target_impls); } } } } } fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clean::Struct) { let mut sidebar = Buffer::new(); let fields = get_struct_fields_name(&s.fields); if !fields.is_empty() { if let CtorKind::Fictive = s.struct_type { sidebar.push_str( "<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\ <div class=\"sidebar-links\">", ); for field in fields { sidebar.push_str(&field); } sidebar.push_str("</div>"); } } sidebar_assoc_items(cx, &mut sidebar, it); if !sidebar.is_empty() { write!(buf, "<div class=\"block items\">{}</div>", sidebar.into_inner()); } } fn get_id_for_impl_on_foreign_type( for_: &clean::Type, trait_: &clean::Type, cx: &Context<'_>, ) -> String { small_url_encode(format!("impl-{:#}-for-{:#}", trait_.print(cx), for_.print(cx),)) } fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { match *item.kind { clean::ItemKind::ImplItem(ref i) => { if let Some(ref trait_) = i.trait_ { // Alternative format produces no URLs, // so this parameter does nothing. Some(( format!("{:#}", i.for_.print(cx)), get_id_for_impl_on_foreign_type(&i.for_, trait_, cx), )) } else { None } } _ => None, } } fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) { buf.write_str("<div class=\"block items\">"); fn print_sidebar_section( out: &mut Buffer, items: &[clean::Item], before: &str, filter: impl Fn(&clean::Item) -> bool, write: impl Fn(&mut Buffer, &str), after: &str, ) { let mut items = items .iter() .filter_map(|m| match m.name { Some(ref name) if filter(m) => Some(name.as_str()), _ => None, }) .collect::<Vec<_>>(); if !items.is_empty() { items.sort_unstable(); out.push_str(before); for item in items.into_iter() { write(out, &item); } out.push_str(after); } } print_sidebar_section( buf, &t.items, "<a class=\"sidebar-title\" href=\"#associated-types\">\ Associated Types</a><div class=\"sidebar-links\">", |m| m.is_associated_type(), |out, sym| write!(out, "<a href=\"#associatedtype.{0}\">{0}</a>", sym), "</div>", ); print_sidebar_section( buf, &t.items, "<a class=\"sidebar-title\" href=\"#associated-const\">\ Associated Constants</a><div class=\"sidebar-links\">", |m| m.is_associated_const(), |out, sym| write!(out, "<a href=\"#associatedconstant.{0}\">{0}</a>", sym), "</div>", ); print_sidebar_section( buf, &t.items, "<a class=\"sidebar-title\" href=\"#required-methods\">\ Required Methods</a><div class=\"sidebar-links\">", |m| m.is_ty_method(), |out, sym| write!(out, "<a href=\"#tymethod.{0}\">{0}</a>", sym), "</div>", ); print_sidebar_section( buf, &t.items, "<a class=\"sidebar-title\" href=\"#provided-methods\">\ Provided Methods</a><div class=\"sidebar-links\">", |m| m.is_method(), |out, sym| write!(out, "<a href=\"#method.{0}\">{0}</a>", sym), "</div>", ); if let Some(implementors) = cx.cache.implementors.get(&it.def_id.expect_real()) { let cache = cx.cache(); let mut res = implementors .iter() .filter(|i| { i.inner_impl() .for_ .def_id_full(cache) .map_or(false, |d| !cx.cache.paths.contains_key(&d)) }) .filter_map(|i| extract_for_impl_name(&i.impl_item, cx)) .collect::<Vec<_>>(); if !res.is_empty() { res.sort(); buf.push_str( "<a class=\"sidebar-title\" href=\"#foreign-impls\">\ Implementations on Foreign Types</a>\ <div class=\"sidebar-links\">", ); for (name, id) in res.into_iter() { write!(buf, "<a href=\"#{}\">{}</a>", id, Escape(&name)); } buf.push_str("</div>"); } } sidebar_assoc_items(cx, buf, it); buf.push_str("<a class=\"sidebar-title\" href=\"#implementors\">Implementors</a>"); if t.is_auto { buf.push_str( "<a class=\"sidebar-title\" \ href=\"#synthetic-implementors\">Auto Implementors</a>", ); } buf.push_str("</div>") } fn sidebar_primitive(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { let mut sidebar = Buffer::new(); sidebar_assoc_items(cx, &mut sidebar, it); if !sidebar.is_empty() { write!(buf, "<div class=\"block items\">{}</div>", sidebar.into_inner()); } } fn sidebar_typedef(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { let mut sidebar = Buffer::new(); sidebar_assoc_items(cx, &mut sidebar, it); if !sidebar.is_empty() { write!(buf, "<div class=\"block items\">{}</div>", sidebar.into_inner()); } } fn get_struct_fields_name(fields: &[clean::Item]) -> Vec<String> { let mut fields = fields .iter() .filter(|f| matches!(*f.kind, clean::StructFieldItem(..))) .filter_map(|f| { f.name.map(|name| format!("<a href=\"#structfield.{name}\">{name}</a>", name = name)) }) .collect::<Vec<_>>(); fields.sort(); fields } fn sidebar_union(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, u: &clean::Union) { let mut sidebar = Buffer::new(); let fields = get_struct_fields_name(&u.fields); if !fields.is_empty() { sidebar.push_str( "<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\ <div class=\"sidebar-links\">", ); for field in fields { sidebar.push_str(&field); } sidebar.push_str("</div>"); } sidebar_assoc_items(cx, &mut sidebar, it); if !sidebar.is_empty() { write!(buf, "<div class=\"block items\">{}</div>", sidebar.into_inner()); } } fn sidebar_enum(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, e: &clean::Enum) { let mut sidebar = Buffer::new(); let mut variants = e .variants .iter() .filter_map(|v| match v.name { Some(ref name) => Some(format!("<a href=\"#variant.{name}\">{name}</a>", name = name)), _ => None, }) .collect::<Vec<_>>(); if !variants.is_empty() { variants.sort_unstable(); sidebar.push_str(&format!( "<a class=\"sidebar-title\" href=\"#variants\">Variants</a>\ <div class=\"sidebar-links\">{}</div>", variants.join(""), )); } sidebar_assoc_items(cx, &mut sidebar, it); if !sidebar.is_empty() { write!(buf, "<div class=\"block items\">{}</div>", sidebar.into_inner()); } } fn item_ty_to_strs(ty: ItemType) -> (&'static str, &'static str) { match ty { ItemType::ExternCrate | ItemType::Import => ("reexports", "Re-exports"), ItemType::Module => ("modules", "Modules"), ItemType::Struct => ("structs", "Structs"), ItemType::Union => ("unions", "Unions"), ItemType::Enum => ("enums", "Enums"), ItemType::Function => ("functions", "Functions"), ItemType::Typedef => ("types", "Type Definitions"), ItemType::Static => ("statics", "Statics"), ItemType::Constant => ("constants", "Constants"), ItemType::Trait => ("traits", "Traits"), ItemType::Impl => ("impls", "Implementations"), ItemType::TyMethod => ("tymethods", "Type Methods"), ItemType::Method => ("methods", "Methods"), ItemType::StructField => ("fields", "Struct Fields"), ItemType::Variant => ("variants", "Variants"), ItemType::Macro => ("macros", "Macros"), ItemType::Primitive => ("primitives", "Primitive Types"), ItemType::AssocType => ("associated-types", "Associated Types"), ItemType::AssocConst => ("associated-consts", "Associated Constants"), ItemType::ForeignType => ("foreign-types", "Foreign Types"), ItemType::Keyword => ("keywords", "Keywords"), ItemType::OpaqueTy => ("opaque-types", "Opaque Types"), ItemType::ProcAttribute => ("attributes", "Attribute Macros"), ItemType::ProcDerive => ("derives", "Derive Macros"), ItemType::TraitAlias => ("trait-aliases", "Trait aliases"), } } fn sidebar_module(buf: &mut Buffer, items: &[clean::Item]) { let mut sidebar = String::new(); // Re-exports are handled a bit differently because they can be extern crates or imports. if items.iter().any(|it| { it.name.is_some() && (it.type_() == ItemType::ExternCrate || (it.type_() == ItemType::Import && !it.is_stripped())) }) { let (id, name) = item_ty_to_strs(ItemType::Import); sidebar.push_str(&format!("<li><a href=\"#{}\">{}</a></li>", id, name)); } // ordering taken from item_module, reorder, where it prioritized elements in a certain order // to print its headings for &myty in &[ ItemType::Primitive, ItemType::Module, ItemType::Macro, ItemType::Struct, ItemType::Enum, ItemType::Constant, ItemType::Static, ItemType::Trait, ItemType::Function, ItemType::Typedef, ItemType::Union, ItemType::Impl, ItemType::TyMethod, ItemType::Method, ItemType::StructField, ItemType::Variant, ItemType::AssocType, ItemType::AssocConst, ItemType::ForeignType, ItemType::Keyword, ] { if items.iter().any(|it| !it.is_stripped() && it.type_() == myty && it.name.is_some()) { let (id, name) = item_ty_to_strs(myty); sidebar.push_str(&format!("<li><a href=\"#{}\">{}</a></li>", id, name)); } } if !sidebar.is_empty() { write!(buf, "<div class=\"block items\"><ul>{}</ul></div>", sidebar); } } fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { let mut sidebar = Buffer::new(); sidebar_assoc_items(cx, &mut sidebar, it); if !sidebar.is_empty() { write!(buf, "<div class=\"block items\">{}</div>", sidebar.into_inner()); } } crate const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang"; /// 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<String> { let mut out = Vec::new(); let mut visited = FxHashSet::default(); let mut work = VecDeque::new(); work.push_back(first_ty); while let Some(ty) = work.pop_front() { if !visited.insert(ty.clone()) { continue; } match ty { clean::Type::ResolvedPath { did, .. } => { let get_extern = || cache.external_paths.get(&did).map(|s| s.0.clone()); let fqp = cache.exact_paths.get(&did).cloned().or_else(get_extern); if let Some(path) = fqp { out.push(path.join("::")); } } clean::Type::Tuple(tys) => { work.extend(tys.into_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 { self_type, trait_, .. } => { work.push_back(*self_type); work.push_back(*trait_); } _ => {} } } out }