diff options
Diffstat (limited to 'src/librustdoc/html/render')
| -rw-r--r-- | src/librustdoc/html/render/cache.rs | 500 | ||||
| -rw-r--r-- | src/librustdoc/html/render/mod.rs | 4589 |
2 files changed, 4601 insertions, 488 deletions
diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs index 1b5c8a9378e..378efa1a1be 100644 --- a/src/librustdoc/html/render/cache.rs +++ b/src/librustdoc/html/render/cache.rs @@ -1,18 +1,16 @@ -use crate::clean::{self, AttributesExt, GetDefId}; -use crate::fold::DocFolder; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX}; -use rustc_middle::middle::privacy::AccessLevels; -use rustc_span::source_map::FileName; -use rustc_span::symbol::sym; use std::collections::BTreeMap; -use std::mem; -use std::path::{Path, PathBuf}; +use std::path::Path; +use rustc_data_structures::fx::FxHashMap; +use rustc_span::symbol::sym; use serde::Serialize; -use super::{plain_summary_line, shorten, Impl, IndexItem, IndexItemFunctionType, ItemType}; -use super::{Generic, RenderInfo, RenderType, TypeWithKind}; +use crate::clean::types::GetDefId; +use crate::clean::{self, AttributesExt}; +use crate::formats::cache::Cache; +use crate::formats::item_type::ItemType; +use crate::html::render::{plain_summary_line, shorten}; +use crate::html::render::{Generic, IndexItem, IndexItemFunctionType, RenderType, TypeWithKind}; /// Indicates where an external crate can be found. pub enum ExternalLocation { @@ -24,483 +22,9 @@ pub enum ExternalLocation { Unknown, } -/// This cache is used to store information about the `clean::Crate` being -/// rendered in order to provide more useful documentation. This contains -/// information like all implementors of a trait, all traits a type implements, -/// documentation for all known traits, etc. -/// -/// This structure purposefully does not implement `Clone` because it's intended -/// to be a fairly large and expensive structure to clone. Instead this adheres -/// to `Send` so it may be stored in a `Arc` instance and shared among the various -/// rendering threads. -#[derive(Default)] -crate struct Cache { - /// Maps a type ID to all known implementations for that type. This is only - /// recognized for intra-crate `ResolvedPath` types, and is used to print - /// out extra documentation on the page of an enum/struct. - /// - /// The values of the map are a list of implementations and documentation - /// found on that implementation. - pub impls: FxHashMap<DefId, Vec<Impl>>, - - /// Maintains a mapping of local crate `DefId`s to the fully qualified name - /// and "short type description" of that node. This is used when generating - /// URLs when a type is being linked to. External paths are not located in - /// this map because the `External` type itself has all the information - /// necessary. - pub paths: FxHashMap<DefId, (Vec<String>, ItemType)>, - - /// Similar to `paths`, but only holds external paths. This is only used for - /// generating explicit hyperlinks to other crates. - pub external_paths: FxHashMap<DefId, (Vec<String>, ItemType)>, - - /// Maps local `DefId`s of exported types to fully qualified paths. - /// Unlike 'paths', this mapping ignores any renames that occur - /// due to 'use' statements. - /// - /// This map is used when writing out the special 'implementors' - /// javascript file. By using the exact path that the type - /// is declared with, we ensure that each path will be identical - /// to the path used if the corresponding type is inlined. By - /// doing this, we can detect duplicate impls on a trait page, and only display - /// the impl for the inlined type. - pub exact_paths: FxHashMap<DefId, Vec<String>>, - - /// This map contains information about all known traits of this crate. - /// Implementations of a crate should inherit the documentation of the - /// parent trait if no extra documentation is specified, and default methods - /// should show up in documentation about trait implementations. - pub traits: FxHashMap<DefId, clean::Trait>, - - /// When rendering traits, it's often useful to be able to list all - /// implementors of the trait, and this mapping is exactly, that: a mapping - /// of trait ids to the list of known implementors of the trait - pub implementors: FxHashMap<DefId, Vec<Impl>>, - - /// Cache of where external crate documentation can be found. - pub extern_locations: FxHashMap<CrateNum, (String, PathBuf, ExternalLocation)>, - - /// Cache of where documentation for primitives can be found. - pub primitive_locations: FxHashMap<clean::PrimitiveType, DefId>, - - // Note that external items for which `doc(hidden)` applies to are shown as - // non-reachable while local items aren't. This is because we're reusing - // the access levels from the privacy check pass. - pub access_levels: AccessLevels<DefId>, - - /// The version of the crate being documented, if given from the `--crate-version` flag. - pub crate_version: Option<String>, - - /// Whether to document private items. - /// This is stored in `Cache` so it doesn't need to be passed through all rustdoc functions. - pub document_private: bool, - - // Private fields only used when initially crawling a crate to build a cache - stack: Vec<String>, - parent_stack: Vec<DefId>, - parent_is_trait_impl: bool, - search_index: Vec<IndexItem>, - stripped_mod: bool, - pub deref_trait_did: Option<DefId>, - pub deref_mut_trait_did: Option<DefId>, - pub owned_box_did: Option<DefId>, - masked_crates: FxHashSet<CrateNum>, - - // In rare case where a structure is defined in one module but implemented - // in another, if the implementing module is parsed before defining module, - // then the fully qualified name of the structure isn't presented in `paths` - // yet when its implementation methods are being indexed. Caches such methods - // and their parent id here and indexes them at the end of crate parsing. - orphan_impl_items: Vec<(DefId, clean::Item)>, - - // Similarly to `orphan_impl_items`, sometimes trait impls are picked up - // even though the trait itself is not exported. This can happen if a trait - // was defined in function/expression scope, since the impl will be picked - // up by `collect-trait-impls` but the trait won't be scraped out in the HIR - // crawl. In order to prevent crashes when looking for spotlight traits or - // when gathering trait documentation on a type, hold impls here while - // folding and add them to the cache later on if we find the trait. - orphan_trait_impls: Vec<(DefId, FxHashSet<DefId>, Impl)>, - - /// Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias, - /// we need the alias element to have an array of items. - pub(super) aliases: BTreeMap<String, Vec<usize>>, -} - -impl Cache { - pub fn from_krate( - renderinfo: RenderInfo, - document_private: bool, - extern_html_root_urls: &BTreeMap<String, String>, - dst: &Path, - mut krate: clean::Crate, - ) -> (clean::Crate, String, Cache) { - // Crawl the crate to build various caches used for the output - let RenderInfo { - inlined: _, - external_paths, - exact_paths, - access_levels, - deref_trait_did, - deref_mut_trait_did, - owned_box_did, - .. - } = renderinfo; - - let external_paths = - external_paths.into_iter().map(|(k, (v, t))| (k, (v, ItemType::from(t)))).collect(); - - let mut cache = Cache { - impls: Default::default(), - external_paths, - exact_paths, - paths: Default::default(), - implementors: Default::default(), - stack: Vec::new(), - parent_stack: Vec::new(), - search_index: Vec::new(), - parent_is_trait_impl: false, - extern_locations: Default::default(), - primitive_locations: Default::default(), - stripped_mod: false, - access_levels, - crate_version: krate.version.take(), - document_private, - orphan_impl_items: Vec::new(), - orphan_trait_impls: Vec::new(), - traits: krate.external_traits.replace(Default::default()), - deref_trait_did, - deref_mut_trait_did, - owned_box_did, - masked_crates: mem::take(&mut krate.masked_crates), - aliases: Default::default(), - }; - - // Cache where all our extern crates are located - for &(n, ref e) in &krate.externs { - let src_root = match e.src { - FileName::Real(ref p) => match p.local_path().parent() { - Some(p) => p.to_path_buf(), - None => PathBuf::new(), - }, - _ => PathBuf::new(), - }; - let extern_url = extern_html_root_urls.get(&e.name).map(|u| &**u); - cache - .extern_locations - .insert(n, (e.name.clone(), src_root, extern_location(e, extern_url, &dst))); - - let did = DefId { krate: n, index: CRATE_DEF_INDEX }; - cache.external_paths.insert(did, (vec![e.name.to_string()], ItemType::Module)); - } - - // Cache where all known primitives have their documentation located. - // - // Favor linking to as local extern as possible, so iterate all crates in - // reverse topological order. - for &(_, ref e) in krate.externs.iter().rev() { - for &(def_id, prim, _) in &e.primitives { - cache.primitive_locations.insert(prim, def_id); - } - } - for &(def_id, prim, _) in &krate.primitives { - cache.primitive_locations.insert(prim, def_id); - } - - cache.stack.push(krate.name.clone()); - krate = cache.fold_crate(krate); - - for (trait_did, dids, impl_) in cache.orphan_trait_impls.drain(..) { - if cache.traits.contains_key(&trait_did) { - for did in dids { - cache.impls.entry(did).or_insert(vec![]).push(impl_.clone()); - } - } - } - - // Build our search index - let index = build_index(&krate, &mut cache); - - (krate, index, cache) - } -} - -impl DocFolder for Cache { - fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { - if item.def_id.is_local() { - debug!("folding {} \"{:?}\", id {:?}", item.type_(), item.name, item.def_id); - } - - // If this is a stripped module, - // we don't want it or its children in the search index. - let orig_stripped_mod = match item.inner { - clean::StrippedItem(box clean::ModuleItem(..)) => { - mem::replace(&mut self.stripped_mod, true) - } - _ => self.stripped_mod, - }; - - // If the impl is from a masked crate or references something from a - // masked crate then remove it completely. - if let clean::ImplItem(ref i) = item.inner { - if self.masked_crates.contains(&item.def_id.krate) - || i.trait_.def_id().map_or(false, |d| self.masked_crates.contains(&d.krate)) - || i.for_.def_id().map_or(false, |d| self.masked_crates.contains(&d.krate)) - { - return None; - } - } - - // Propagate a trait method's documentation to all implementors of the - // trait. - if let clean::TraitItem(ref t) = item.inner { - self.traits.entry(item.def_id).or_insert_with(|| t.clone()); - } - - // Collect all the implementors of traits. - if let clean::ImplItem(ref i) = item.inner { - if let Some(did) = i.trait_.def_id() { - if i.blanket_impl.is_none() { - self.implementors - .entry(did) - .or_default() - .push(Impl { impl_item: item.clone() }); - } - } - } - - // Index this method for searching later on. - if let Some(ref s) = item.name { - let (parent, is_inherent_impl_item) = match item.inner { - clean::StrippedItem(..) => ((None, None), false), - clean::AssocConstItem(..) | clean::TypedefItem(_, true) - if self.parent_is_trait_impl => - { - // skip associated items in trait impls - ((None, None), false) - } - clean::AssocTypeItem(..) - | clean::TyMethodItem(..) - | clean::StructFieldItem(..) - | clean::VariantItem(..) => ( - ( - Some(*self.parent_stack.last().expect("parent_stack is empty")), - Some(&self.stack[..self.stack.len() - 1]), - ), - false, - ), - clean::MethodItem(..) | clean::AssocConstItem(..) => { - if self.parent_stack.is_empty() { - ((None, None), false) - } else { - let last = self.parent_stack.last().expect("parent_stack is empty 2"); - let did = *last; - let path = match self.paths.get(&did) { - // The current stack not necessarily has correlation - // for where the type was defined. On the other - // hand, `paths` always has the right - // information if present. - Some(&( - ref fqp, - ItemType::Trait - | ItemType::Struct - | ItemType::Union - | ItemType::Enum, - )) => Some(&fqp[..fqp.len() - 1]), - Some(..) => Some(&*self.stack), - None => None, - }; - ((Some(*last), path), true) - } - } - _ => ((None, Some(&*self.stack)), false), - }; - - match parent { - (parent, Some(path)) if is_inherent_impl_item || !self.stripped_mod => { - debug_assert!(!item.is_stripped()); - - // A crate has a module at its root, containing all items, - // which should not be indexed. The crate-item itself is - // inserted later on when serializing the search-index. - if item.def_id.index != CRATE_DEF_INDEX { - self.search_index.push(IndexItem { - ty: item.type_(), - name: s.to_string(), - path: path.join("::"), - desc: shorten(plain_summary_line(item.doc_value())), - parent, - parent_idx: None, - search_type: get_index_search_type(&item), - }); - - for alias in item.attrs.get_doc_aliases() { - self.aliases - .entry(alias.to_lowercase()) - .or_insert(Vec::new()) - .push(self.search_index.len() - 1); - } - } - } - (Some(parent), None) if is_inherent_impl_item => { - // We have a parent, but we don't know where they're - // defined yet. Wait for later to index this item. - self.orphan_impl_items.push((parent, item.clone())); - } - _ => {} - } - } - - // Keep track of the fully qualified path for this item. - let pushed = match item.name { - Some(ref n) if !n.is_empty() => { - self.stack.push(n.to_string()); - true - } - _ => false, - }; - - match item.inner { - clean::StructItem(..) - | clean::EnumItem(..) - | clean::TypedefItem(..) - | clean::TraitItem(..) - | clean::FunctionItem(..) - | clean::ModuleItem(..) - | clean::ForeignFunctionItem(..) - | clean::ForeignStaticItem(..) - | clean::ConstantItem(..) - | clean::StaticItem(..) - | clean::UnionItem(..) - | clean::ForeignTypeItem - | clean::MacroItem(..) - | clean::ProcMacroItem(..) - | clean::VariantItem(..) - if !self.stripped_mod => - { - // Re-exported items mean that the same id can show up twice - // in the rustdoc ast that we're looking at. We know, - // however, that a re-exported item doesn't show up in the - // `public_items` map, so we can skip inserting into the - // paths map if there was already an entry present and we're - // not a public item. - if !self.paths.contains_key(&item.def_id) - || self.access_levels.is_public(item.def_id) - { - self.paths.insert(item.def_id, (self.stack.clone(), item.type_())); - } - } - clean::PrimitiveItem(..) => { - self.paths.insert(item.def_id, (self.stack.clone(), item.type_())); - } - - _ => {} - } - - // Maintain the parent stack - let orig_parent_is_trait_impl = self.parent_is_trait_impl; - let parent_pushed = match item.inner { - clean::TraitItem(..) - | clean::EnumItem(..) - | clean::ForeignTypeItem - | clean::StructItem(..) - | clean::UnionItem(..) - | clean::VariantItem(..) => { - self.parent_stack.push(item.def_id); - self.parent_is_trait_impl = false; - true - } - clean::ImplItem(ref i) => { - self.parent_is_trait_impl = i.trait_.is_some(); - match i.for_ { - clean::ResolvedPath { did, .. } => { - self.parent_stack.push(did); - true - } - ref t => { - let prim_did = t - .primitive_type() - .and_then(|t| self.primitive_locations.get(&t).cloned()); - match prim_did { - Some(did) => { - self.parent_stack.push(did); - true - } - None => false, - } - } - } - } - _ => false, - }; - - // Once we've recursively found all the generics, hoard off all the - // implementations elsewhere. - let ret = self.fold_item_recur(item).and_then(|item| { - if let clean::Item { inner: clean::ImplItem(_), .. } = item { - // Figure out the id of this impl. This may map to a - // primitive rather than always to a struct/enum. - // Note: matching twice to restrict the lifetime of the `i` borrow. - let mut dids = FxHashSet::default(); - if let clean::Item { inner: clean::ImplItem(ref i), .. } = item { - match i.for_ { - clean::ResolvedPath { did, .. } - | clean::BorrowedRef { - type_: box clean::ResolvedPath { did, .. }, .. - } => { - dids.insert(did); - } - ref t => { - let did = t - .primitive_type() - .and_then(|t| self.primitive_locations.get(&t).cloned()); - - if let Some(did) = did { - dids.insert(did); - } - } - } - - if let Some(generics) = i.trait_.as_ref().and_then(|t| t.generics()) { - for bound in generics { - if let Some(did) = bound.def_id() { - dids.insert(did); - } - } - } - } else { - unreachable!() - }; - let impl_item = Impl { impl_item: item }; - if impl_item.trait_did().map_or(true, |d| self.traits.contains_key(&d)) { - for did in dids { - self.impls.entry(did).or_insert(vec![]).push(impl_item.clone()); - } - } else { - let trait_did = impl_item.trait_did().expect("no trait did"); - self.orphan_trait_impls.push((trait_did, dids, impl_item)); - } - None - } else { - Some(item) - } - }); - - if pushed { - self.stack.pop().expect("stack already empty"); - } - if parent_pushed { - self.parent_stack.pop().expect("parent stack already empty"); - } - self.stripped_mod = orig_stripped_mod; - self.parent_is_trait_impl = orig_parent_is_trait_impl; - ret - } -} - /// Attempts to find where an external crate is located, given that we're /// rendering in to the specified source destination. -fn extern_location( +pub fn extern_location( e: &clean::ExternalCrate, extern_url: Option<&str>, dst: &Path, @@ -538,7 +62,7 @@ fn extern_location( } /// Builds the search index from the collected metadata -fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { +pub fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { let mut defid_to_pathid = FxHashMap::default(); let mut crate_items = Vec::with_capacity(cache.search_index.len()); let mut crate_paths = vec![]; @@ -640,7 +164,7 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { ) } -fn get_index_search_type(item: &clean::Item) -> Option<IndexItemFunctionType> { +crate fn get_index_search_type(item: &clean::Item) -> Option<IndexItemFunctionType> { let (all_types, ret_types) = match item.inner { clean::FunctionItem(ref f) => (&f.all_types, &f.ret_types), clean::MethodItem(ref m) => (&m.all_types, &m.ret_types), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs new file mode 100644 index 00000000000..5fb2d9f6f91 --- /dev/null +++ b/src/librustdoc/html/render/mod.rs @@ -0,0 +1,4589 @@ +// ignore-tidy-filelength + +//! 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. + +pub mod cache; + +#[cfg(test)] +mod tests; + +use std::borrow::Cow; +use std::cell::{Cell, RefCell}; +use std::cmp::Ordering; +use std::collections::{BTreeMap, VecDeque}; +use std::default::Default; +use std::ffi::OsStr; +use std::fmt::{self, Write}; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::io::{self, BufReader}; +use std::path::{Component, Path, PathBuf}; +use std::rc::Rc; +use std::str; +use std::string::ToString; +use std::sync::mpsc::{channel, Receiver}; +use std::sync::Arc; + +use itertools::Itertools; +use rustc_ast_pretty::pprust; +use rustc_data_structures::flock; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_feature::UnstableFeatures; +use rustc_hir as hir; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::Mutability; +use rustc_middle::middle::stability; +use rustc_span::edition::Edition; +use rustc_span::hygiene::MacroKind; +use rustc_span::source_map::FileName; +use rustc_span::symbol::{sym, Symbol}; +use serde::ser::SerializeSeq; +use serde::{Serialize, Serializer}; + +use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, TypeKind}; +use crate::config::RenderInfo; +use crate::config::RenderOptions; +use crate::docfs::{DocFS, PathError}; +use crate::doctree; +use crate::error::Error; +use crate::formats::cache::{cache, Cache}; +use crate::formats::item_type::ItemType; +use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode}; +use crate::html::escape::Escape; +use crate::html::format::fmt_impl_for_trait_page; +use crate::html::format::Function; +use crate::html::format::{href, print_default_space, print_generic_bounds, WhereClause}; +use crate::html::format::{print_abi_with_space, Buffer, PrintWithSpace}; +use crate::html::markdown::{self, ErrorCodes, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine}; +use crate::html::sources; +use crate::html::{highlight, layout, static_files}; +use cache::{build_index, ExternalLocation}; + +/// A pair of name and its optional document. +pub 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 { write!(f, "{}", v) } + }) +} + +/// Major driving force in all rustdoc rendering. This contains information +/// about where in the tree-like hierarchy rendering is occurring and controls +/// how the current page is being rendered. +/// +/// It is intended that this context is a lightweight object which can be fairly +/// easily cloned because it is cloned per work-job (about once per item in the +/// rustdoc tree). +#[derive(Clone)] +crate struct Context { + /// Current hierarchy of components leading down to what's currently being + /// rendered + pub current: Vec<String>, + /// The current destination folder of where HTML artifacts should be placed. + /// This changes as the context descends into the module hierarchy. + pub dst: PathBuf, + /// A flag, which when `true`, will render pages which redirect to the + /// real location of an item. This is used to allow external links to + /// publicly reused items to redirect to the right location. + pub render_redirect_pages: bool, + /// The map used to ensure all generated 'id=' attributes are unique. + id_map: Rc<RefCell<IdMap>>, + pub shared: Arc<SharedContext>, + all: Rc<RefCell<AllTypes>>, + /// Storage for the errors produced while generating documentation so they + /// can be printed together at the end. + pub errors: Rc<Receiver<String>>, +} + +crate struct SharedContext { + /// The path to the crate root source minus the file name. + /// Used for simplifying paths to the highlighted source code files. + pub src_root: PathBuf, + /// This describes the layout of each page, and is not modified after + /// creation of the context (contains info like the favicon and added html). + pub layout: layout::Layout, + /// This flag indicates whether `[src]` links should be generated or not. If + /// the source files are present in the html rendering, then this will be + /// `true`. + pub include_sources: bool, + /// The local file sources we've emitted and their respective url-paths. + pub local_sources: FxHashMap<PathBuf, String>, + /// Whether the collapsed pass ran + pub collapsed: bool, + /// The base-URL of the issue tracker for when an item has been tagged with + /// an issue number. + pub issue_tracker_base_url: Option<String>, + /// The directories that have already been created in this doc run. Used to reduce the number + /// of spurious `create_dir_all` calls. + pub created_dirs: RefCell<FxHashSet<PathBuf>>, + /// This flag indicates whether listings of modules (in the side bar and documentation itself) + /// should be ordered alphabetically or in order of appearance (in the source code). + pub sort_modules_alphabetically: bool, + /// Additional CSS files to be added to the generated docs. + pub style_files: Vec<StylePath>, + /// Suffix to be added on resource files (if suffix is "-v2" then "light.css" becomes + /// "light-v2.css"). + pub resource_suffix: String, + /// Optional path string to be used to load static files on output pages. If not set, uses + /// combinations of `../` to reach the documentation root. + pub static_root_path: Option<String>, + /// The fs handle we are working with. + pub fs: DocFS, + /// The default edition used to parse doctests. + pub edition: Edition, + pub codes: ErrorCodes, + playground: Option<markdown::Playground>, +} + +impl Context { + fn path(&self, filename: &str) -> PathBuf { + // We use splitn vs Path::extension here because we might get a filename + // like `style.min.css` and we want to process that into + // `style-suffix.min.css`. Path::extension would just return `css` + // which would result in `style.min-suffix.css` which isn't what we + // want. + let mut iter = filename.splitn(2, '.'); + let base = iter.next().unwrap(); + let ext = iter.next().unwrap(); + let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext,); + self.dst.join(&filename) + } +} + +impl SharedContext { + crate fn ensure_dir(&self, dst: &Path) -> Result<(), Error> { + let mut dirs = self.created_dirs.borrow_mut(); + if !dirs.contains(dst) { + try_err!(self.fs.create_dir_all(dst), dst); + dirs.insert(dst.to_path_buf()); + } + + Ok(()) + } + + /// Based on whether the `collapse-docs` pass was run, return either the `doc_value` or the + /// `collapsed_doc_value` of the given item. + pub fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<Cow<'a, str>> { + if self.collapsed { + item.collapsed_doc_value().map(|s| s.into()) + } else { + item.doc_value().map(|s| s.into()) + } + } +} + +// 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 struct IndexItem { + pub ty: ItemType, + pub name: String, + pub path: String, + pub desc: String, + pub parent: Option<DefId>, + pub parent_idx: Option<usize>, + pub search_type: Option<IndexItemFunctionType>, +} + +impl Serialize for IndexItem { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + assert_eq!( + self.parent.is_some(), + self.parent_idx.is_some(), + "`{}` is missing idx", + self.name + ); + + (self.ty, &self.name, &self.path, &self.desc, self.parent_idx, &self.search_type) + .serialize(serializer) + } +} + +/// 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)] +pub 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: TypeKind, +} + +impl From<(RenderType, TypeKind)> for TypeWithKind { + fn from(x: (RenderType, TypeKind)) -> 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, + { + let mut seq = serializer.serialize_seq(None)?; + seq.serialize_element(&self.ty.name)?; + let x: ItemType = self.kind.into(); + seq.serialize_element(&x)?; + seq.end() + } +} + +#[derive(Debug, Clone)] +pub struct StylePath { + /// The path to the theme + pub path: PathBuf, + /// What the `disabled` attribute should be set to in the HTML tag + pub disabled: bool, +} + +thread_local!(pub static CURRENT_DEPTH: Cell<usize> = Cell::new(0)); + +pub fn initial_ids() -> Vec<String> { + [ + "main", + "search", + "help", + "TOC", + "render-detail", + "associated-types", + "associated-const", + "required-methods", + "provided-methods", + "implementors", + "synthetic-implementors", + "implementors-list", + "synthetic-implementors-list", + "methods", + "deref-methods", + "implementations", + ] + .iter() + .map(|id| (String::from(*id))) + .collect() +} + +/// Generates the documentation for `crate` into the directory `dst` +impl FormatRenderer for Context { + fn init( + mut krate: clean::Crate, + options: RenderOptions, + _render_info: RenderInfo, + edition: Edition, + cache: &mut Cache, + ) -> Result<(Context, clean::Crate), Error> { + // need to save a copy of the options for rendering the index page + let md_opts = options.clone(); + let RenderOptions { + output, + external_html, + id_map, + playground_url, + sort_modules_alphabetically, + themes: style_files, + extension_css, + resource_suffix, + static_root_path, + generate_search_filter, + .. + } = options; + + let src_root = match krate.src { + FileName::Real(ref p) => match p.local_path().parent() { + Some(p) => p.to_path_buf(), + None => PathBuf::new(), + }, + _ => PathBuf::new(), + }; + // If user passed in `--playground-url` arg, we fill in crate name here + let mut playground = None; + if let Some(url) = playground_url { + playground = Some(markdown::Playground { crate_name: Some(krate.name.clone()), url }); + } + let mut layout = layout::Layout { + logo: String::new(), + favicon: String::new(), + external_html, + krate: krate.name.clone(), + css_file_extension: extension_css, + generate_search_filter, + }; + let mut issue_tracker_base_url = None; + let mut include_sources = true; + + // Crawl the crate attributes looking for attributes which control how we're + // going to emit HTML + if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) { + for attr in attrs.lists(sym::doc) { + match (attr.name_or_empty(), attr.value_str()) { + (sym::html_favicon_url, Some(s)) => { + layout.favicon = s.to_string(); + } + (sym::html_logo_url, Some(s)) => { + layout.logo = s.to_string(); + } + (sym::html_playground_url, Some(s)) => { + playground = Some(markdown::Playground { + crate_name: Some(krate.name.clone()), + url: s.to_string(), + }); + } + (sym::issue_tracker_base_url, Some(s)) => { + issue_tracker_base_url = Some(s.to_string()); + } + (sym::html_no_source, None) if attr.is_word() => { + include_sources = false; + } + _ => {} + } + } + } + let (sender, receiver) = channel(); + let mut scx = SharedContext { + collapsed: krate.collapsed, + src_root, + include_sources, + local_sources: Default::default(), + issue_tracker_base_url, + layout, + created_dirs: Default::default(), + sort_modules_alphabetically, + style_files, + resource_suffix, + static_root_path, + fs: DocFS::new(sender), + edition, + codes: ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build()), + playground, + }; + + // Add the default themes to the `Vec` of stylepaths + // + // Note that these must be added before `sources::render` is called + // so that the resulting source pages are styled + // + // `light.css` is not disabled because it is the stylesheet that stays loaded + // by the browser as the theme stylesheet. The theme system (hackily) works by + // changing the href to this stylesheet. All other themes are disabled to + // prevent rule conflicts + scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false }); + scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true }); + scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true }); + + let dst = output; + scx.ensure_dir(&dst)?; + krate = sources::render(&dst, &mut scx, krate)?; + + // Build our search index + let index = build_index(&krate, cache); + + let cache = Arc::new(cache); + let mut cx = Context { + current: Vec::new(), + dst, + render_redirect_pages: false, + id_map: Rc::new(RefCell::new(id_map)), + shared: Arc::new(scx), + all: Rc::new(RefCell::new(AllTypes::new())), + errors: Rc::new(receiver), + }; + + CURRENT_DEPTH.with(|s| s.set(0)); + + // Write shared runs within a flock; disable thread dispatching of IO temporarily. + Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); + write_shared(&cx, &krate, index, &md_opts, &cache)?; + Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + Ok((cx, krate)) + } + + fn after_run(&mut self, diag: &rustc_errors::Handler) -> Result<(), Error> { + Arc::get_mut(&mut self.shared).unwrap().fs.close(); + let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count(); + if nb_errors > 0 { + Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) + } else { + Ok(()) + } + } + + fn after_krate(&mut self, krate: &clean::Crate, cache: &Cache) -> Result<(), Error> { + let final_file = self.dst.join(&krate.name).join("all.html"); + let settings_file = self.dst.join("settings.html"); + let crate_name = krate.name.clone(); + + let mut root_path = self.dst.to_str().expect("invalid path").to_owned(); + if !root_path.ends_with('/') { + root_path.push('/'); + } + let mut page = layout::Page { + title: "List of all items in this crate", + css_class: "mod", + root_path: "../", + static_root_path: self.shared.static_root_path.as_deref(), + description: "List of all items in this crate", + keywords: BASIC_KEYWORDS, + resource_suffix: &self.shared.resource_suffix, + extra_scripts: &[], + static_extra_scripts: &[], + }; + let sidebar = if let Some(ref version) = cache.crate_version { + format!( + "<p class='location'>Crate {}</p>\ + <div class='block version'>\ + <p>Version {}</p>\ + </div>\ + <a id='all-types' href='index.html'><p>Back to index</p></a>", + crate_name, + Escape(version), + ) + } else { + String::new() + }; + let all = self.all.replace(AllTypes::new()); + let v = layout::render( + &self.shared.layout, + &page, + sidebar, + |buf: &mut Buffer| all.print(buf), + &self.shared.style_files, + ); + self.shared.fs.write(&final_file, v.as_bytes())?; + + // Generating settings page. + page.title = "Rustdoc settings"; + page.description = "Settings of Rustdoc"; + page.root_path = "./"; + + let mut style_files = self.shared.style_files.clone(); + let sidebar = "<p class='location'>Settings</p><div class='sidebar-elems'></div>"; + style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false }); + let v = layout::render( + &self.shared.layout, + &page, + sidebar, + settings( + self.shared.static_root_path.as_deref().unwrap_or("./"), + &self.shared.resource_suffix, + ), + &style_files, + ); + self.shared.fs.write(&settings_file, v.as_bytes())?; + Ok(()) + } + + fn mod_item_in( + &mut self, + item: &clean::Item, + item_name: &str, + cache: &Cache, + ) -> Result<(), Error> { + // Stripped modules survive the rustdoc passes (i.e., `strip-private`) + // if they contain impls for public types. These modules can also + // contain items such as publicly re-exported structures. + // + // External crates will provide links to these structures, so + // these modules are recursed into, but not rendered normally + // (a flag on the context). + if !self.render_redirect_pages { + self.render_redirect_pages = item.is_stripped(); + } + let scx = &self.shared; + self.dst.push(item_name); + self.current.push(item_name.to_owned()); + + info!("Recursing into {}", self.dst.display()); + + let buf = self.render_item(item, false, cache); + // buf will be empty if the module is stripped and there is no redirect for it + if !buf.is_empty() { + self.shared.ensure_dir(&self.dst)?; + let joint_dst = self.dst.join("index.html"); + scx.fs.write(&joint_dst, buf.as_bytes())?; + } + + // Render sidebar-items.js used throughout this module. + if !self.render_redirect_pages { + let module = match item.inner { + clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m, + _ => unreachable!(), + }; + let items = self.build_sidebar_items(module); + let js_dst = self.dst.join("sidebar-items.js"); + let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap()); + scx.fs.write(&js_dst, &v)?; + } + Ok(()) + } + + fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> { + info!("Recursed; leaving {}", self.dst.display()); + + // Go back to where we were at + self.dst.pop(); + self.current.pop(); + Ok(()) + } + + fn item(&mut self, item: clean::Item, cache: &Cache) -> Result<(), Error> { + // Stripped modules survive the rustdoc passes (i.e., `strip-private`) + // if they contain impls for public types. These modules can also + // contain items such as publicly re-exported structures. + // + // External crates will provide links to these structures, so + // these modules are recursed into, but not rendered normally + // (a flag on the context). + if !self.render_redirect_pages { + self.render_redirect_pages = item.is_stripped(); + } + + let buf = self.render_item(&item, true, cache); + // buf will be empty if the item is stripped and there is no redirect for it + if !buf.is_empty() { + let name = item.name.as_ref().unwrap(); + let item_type = item.type_(); + let file_name = &item_path(item_type, name); + self.shared.ensure_dir(&self.dst)?; + let joint_dst = self.dst.join(file_name); + self.shared.fs.write(&joint_dst, buf.as_bytes())?; + + if !self.render_redirect_pages { + self.all.borrow_mut().append(full_path(self, &item), &item_type); + } + // If the item is a macro, redirect from the old macro URL (with !) + // to the new one (without). + if item_type == ItemType::Macro { + let redir_name = format!("{}.{}!.html", item_type, name); + let redir_dst = self.dst.join(redir_name); + let v = layout::redirect(file_name); + self.shared.fs.write(&redir_dst, v.as_bytes())?; + } + } + Ok(()) + } +} + +fn write_shared( + cx: &Context, + krate: &clean::Crate, + search_index: String, + options: &RenderOptions, + cache: &Cache, +) -> Result<(), Error> { + // Write out the shared files. Note that these are shared among all rustdoc + // docs placed in the output directory, so this needs to be a synchronized + // operation with respect to all other rustdocs running around. + let lock_file = cx.dst.join(".lock"); + let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file); + + // Add all the static files. These may already exist, but we just + // overwrite them anyway to make sure that they're fresh and up-to-date. + + write_minify( + &cx.shared.fs, + cx.path("rustdoc.css"), + static_files::RUSTDOC_CSS, + options.enable_minification, + )?; + write_minify( + &cx.shared.fs, + cx.path("settings.css"), + static_files::SETTINGS_CSS, + options.enable_minification, + )?; + write_minify( + &cx.shared.fs, + cx.path("noscript.css"), + static_files::NOSCRIPT_CSS, + options.enable_minification, + )?; + + // To avoid "light.css" to be overwritten, we'll first run over the received themes and only + // then we'll run over the "official" styles. + let mut themes: FxHashSet<String> = FxHashSet::default(); + + for entry in &cx.shared.style_files { + let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path); + let extension = + try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); + + // Handle the official themes + match theme { + "light" => write_minify( + &cx.shared.fs, + cx.path("light.css"), + static_files::themes::LIGHT, + options.enable_minification, + )?, + "dark" => write_minify( + &cx.shared.fs, + cx.path("dark.css"), + static_files::themes::DARK, + options.enable_minification, + )?, + "ayu" => write_minify( + &cx.shared.fs, + cx.path("ayu.css"), + static_files::themes::AYU, + options.enable_minification, + )?, + _ => { + // Handle added third-party themes + let content = try_err!(fs::read(&entry.path), &entry.path); + cx.shared + .fs + .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?; + } + }; + + themes.insert(theme.to_owned()); + } + + let write = |p, c| cx.shared.fs.write(p, c); + if (*cx.shared).layout.logo.is_empty() { + write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?; + } + if (*cx.shared).layout.favicon.is_empty() { + write(cx.path("favicon.ico"), static_files::RUST_FAVICON)?; + } + write(cx.path("brush.svg"), static_files::BRUSH_SVG)?; + write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?; + write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?; + + let mut themes: Vec<&String> = themes.iter().collect(); + themes.sort(); + // To avoid theme switch latencies as much as possible, we put everything theme related + // at the beginning of the html files into another js file. + let theme_js = format!( + r#"var themes = document.getElementById("theme-choices"); +var themePicker = document.getElementById("theme-picker"); + +function showThemeButtonState() {{ + themes.style.display = "block"; + themePicker.style.borderBottomRightRadius = "0"; + themePicker.style.borderBottomLeftRadius = "0"; +}} + +function hideThemeButtonState() {{ + themes.style.display = "none"; + themePicker.style.borderBottomRightRadius = "3px"; + themePicker.style.borderBottomLeftRadius = "3px"; +}} + +function switchThemeButtonState() {{ + if (themes.style.display === "block") {{ + hideThemeButtonState(); + }} else {{ + showThemeButtonState(); + }} +}}; + +function handleThemeButtonsBlur(e) {{ + var active = document.activeElement; + var related = e.relatedTarget; + + if (active.id !== "themePicker" && + (!active.parentNode || active.parentNode.id !== "theme-choices") && + (!related || + (related.id !== "themePicker" && + (!related.parentNode || related.parentNode.id !== "theme-choices")))) {{ + hideThemeButtonState(); + }} +}} + +themePicker.onclick = switchThemeButtonState; +themePicker.onblur = handleThemeButtonsBlur; +{}.forEach(function(item) {{ + var but = document.createElement('button'); + but.textContent = item; + but.onclick = function(el) {{ + switchTheme(currentTheme, mainTheme, item, true); + }}; + but.onblur = handleThemeButtonsBlur; + themes.appendChild(but); +}});"#, + serde_json::to_string(&themes).unwrap() + ); + + write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?; + write_minify( + &cx.shared.fs, + cx.path("main.js"), + static_files::MAIN_JS, + options.enable_minification, + )?; + write_minify( + &cx.shared.fs, + cx.path("settings.js"), + static_files::SETTINGS_JS, + options.enable_minification, + )?; + if cx.shared.include_sources { + write_minify( + &cx.shared.fs, + cx.path("source-script.js"), + static_files::sidebar::SOURCE_SCRIPT, + options.enable_minification, + )?; + } + + { + write_minify( + &cx.shared.fs, + cx.path("storage.js"), + &format!( + "var resourcesSuffix = \"{}\";{}", + cx.shared.resource_suffix, + static_files::STORAGE_JS + ), + options.enable_minification, + )?; + } + + if let Some(ref css) = cx.shared.layout.css_file_extension { + let out = cx.path("theme.css"); + let buffer = try_err!(fs::read_to_string(css), css); + if !options.enable_minification { + cx.shared.fs.write(&out, &buffer)?; + } else { + write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?; + } + } + write_minify( + &cx.shared.fs, + cx.path("normalize.css"), + static_files::NORMALIZE_CSS, + options.enable_minification, + )?; + write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?; + write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?; + write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?; + write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?; + write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?; + write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?; + write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?; + write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?; + write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?; + write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?; + write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?; + write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?; + write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?; + + fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec<String>, Vec<String>)> { + let mut ret = Vec::new(); + let mut krates = Vec::new(); + + if path.exists() { + for line in BufReader::new(File::open(path)?).lines() { + let line = line?; + if !line.starts_with(key) { + continue; + } + if line.starts_with(&format!(r#"{}["{}"]"#, key, krate)) { + continue; + } + ret.push(line.to_string()); + krates.push( + line[key.len() + 2..] + .split('"') + .next() + .map(|s| s.to_owned()) + .unwrap_or_else(String::new), + ); + } + } + Ok((ret, krates)) + } + + fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> { + let mut ret = Vec::new(); + let mut krates = Vec::new(); + + if path.exists() { + for line in BufReader::new(File::open(path)?).lines() { + let line = line?; + if !line.starts_with('"') { + continue; + } + if line.starts_with(&format!("\"{}\"", krate)) { + continue; + } + if line.ends_with(",\\") { + ret.push(line[..line.len() - 2].to_string()); + } else { + // Ends with "\\" (it's the case for the last added crate line) + ret.push(line[..line.len() - 1].to_string()); + } + krates.push( + line.split('"') + .find(|s| !s.is_empty()) + .map(|s| s.to_owned()) + .unwrap_or_else(String::new), + ); + } + } + Ok((ret, krates)) + } + + use std::ffi::OsString; + + #[derive(Debug)] + struct Hierarchy { + elem: OsString, + children: FxHashMap<OsString, Hierarchy>, + elems: FxHashSet<OsString>, + } + + impl Hierarchy { + fn new(elem: OsString) -> Hierarchy { + Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() } + } + + fn to_json_string(&self) -> String { + let mut subs: Vec<&Hierarchy> = self.children.values().collect(); + subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem)); + let mut files = self + .elems + .iter() + .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion"))) + .collect::<Vec<_>>(); + files.sort_unstable_by(|a, b| a.cmp(b)); + let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(","); + let dirs = + if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) }; + let files = files.join(","); + let files = + if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) }; + format!( + "{{\"name\":\"{name}\"{dirs}{files}}}", + name = self.elem.to_str().expect("invalid osstring conversion"), + dirs = dirs, + files = files + ) + } + } + + if cx.shared.include_sources { + let mut hierarchy = Hierarchy::new(OsString::new()); + for source in cx + .shared + .local_sources + .iter() + .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok()) + { + let mut h = &mut hierarchy; + let mut elems = source + .components() + .filter_map(|s| match s { + Component::Normal(s) => Some(s.to_owned()), + _ => None, + }) + .peekable(); + loop { + let cur_elem = elems.next().expect("empty file path"); + if elems.peek().is_none() { + h.elems.insert(cur_elem); + break; + } else { + let e = cur_elem.clone(); + h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e)); + h = h.children.get_mut(&cur_elem).expect("not found child"); + } + } + } + + let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix)); + let (mut all_sources, _krates) = try_err!(collect(&dst, &krate.name, "sourcesIndex"), &dst); + all_sources.push(format!( + "sourcesIndex[\"{}\"] = {};", + &krate.name, + hierarchy.to_json_string() + )); + all_sources.sort(); + let v = format!( + "var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n", + all_sources.join("\n") + ); + cx.shared.fs.write(&dst, v.as_bytes())?; + } + + // Update the search index + let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix)); + let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name), &dst); + all_indexes.push(search_index); + + // Sort the indexes by crate so the file will be generated identically even + // with rustdoc running in parallel. + all_indexes.sort(); + { + let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); + v.push_str(&all_indexes.join(",\\\n")); + // "addSearchOptions" has to be called first so the crate filtering can be set before the + // search might start (if it's set into the URL for example). + v.push_str("\\\n}');\naddSearchOptions(searchIndex);initSearch(searchIndex);"); + cx.shared.fs.write(&dst, &v)?; + } + if options.enable_index_page { + if let Some(index_page) = options.index_page.clone() { + let mut md_opts = options.clone(); + md_opts.output = cx.dst.clone(); + md_opts.external_html = (*cx.shared).layout.external_html.clone(); + + crate::markdown::render(&index_page, md_opts, cx.shared.edition) + .map_err(|e| Error::new(e, &index_page))?; + } else { + let dst = cx.dst.join("index.html"); + let page = layout::Page { + title: "Index of crates", + css_class: "mod", + root_path: "./", + static_root_path: cx.shared.static_root_path.as_deref(), + description: "List of crates", + keywords: BASIC_KEYWORDS, + resource_suffix: &cx.shared.resource_suffix, + extra_scripts: &[], + static_extra_scripts: &[], + }; + krates.push(krate.name.clone()); + krates.sort(); + krates.dedup(); + + let content = format!( + "<h1 class='fqn'>\ + <span class='in-band'>List of all crates</span>\ +</h1><ul class='mod'>{}</ul>", + krates + .iter() + .map(|s| { + format!("<li><a href=\"{}index.html\">{}</li>", ensure_trailing_slash(s), s) + }) + .collect::<String>() + ); + let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files); + cx.shared.fs.write(&dst, v.as_bytes())?; + } + } + + // Update the list of all implementors for traits + let dst = cx.dst.join("implementors"); + for (&did, imps) in &cache.implementors { + // Private modules can leak through to this phase of rustdoc, which + // could contain implementations for otherwise private types. In some + // rare cases we could find an implementation for an item which wasn't + // indexed, so we just skip this step in that case. + // + // FIXME: this is a vague explanation for why this can't be a `get`, in + // theory it should be... + let &(ref remote_path, remote_item_type) = match cache.paths.get(&did) { + Some(p) => p, + None => match cache.external_paths.get(&did) { + Some(p) => p, + None => continue, + }, + }; + + #[derive(Serialize)] + struct Implementor { + text: String, + synthetic: bool, + types: Vec<String>, + } + + let implementors = imps + .iter() + .filter_map(|imp| { + // If the trait and implementation are in the same crate, then + // there's no need to emit information about it (there's inlining + // going on). If they're in different crates then the crate defining + // the trait will be interested in our implementation. + // + // If the implementation is from another crate then that crate + // should add it. + if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() { + None + } else { + Some(Implementor { + text: imp.inner_impl().print().to_string(), + synthetic: imp.inner_impl().synthetic, + types: collect_paths_for_type(imp.inner_impl().for_.clone()), + }) + } + }) + .collect::<Vec<_>>(); + + // Only create a js file if we have impls to add to it. If the trait is + // documented locally though we always create the file to avoid dead + // links. + if implementors.is_empty() && !cache.paths.contains_key(&did) { + continue; + } + + let implementors = format!( + r#"implementors["{}"] = {};"#, + krate.name, + serde_json::to_string(&implementors).unwrap() + ); + + let mut mydst = dst.clone(); + for part in &remote_path[..remote_path.len() - 1] { + mydst.push(part); + } + cx.shared.ensure_dir(&mydst)?; + mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1])); + + let (mut all_implementors, _) = + try_err!(collect(&mydst, &krate.name, "implementors"), &mydst); + all_implementors.push(implementors); + // Sort the implementors by crate so the file will be generated + // identically even with rustdoc running in parallel. + all_implementors.sort(); + + let mut v = String::from("(function() {var implementors = {};\n"); + for implementor in &all_implementors { + writeln!(v, "{}", *implementor).unwrap(); + } + v.push_str( + "if (window.register_implementors) {\ + window.register_implementors(implementors);\ + } else {\ + window.pending_implementors = implementors;\ + }", + ); + v.push_str("})()"); + cx.shared.fs.write(&mydst, &v)?; + } + Ok(()) +} + +fn write_minify( + fs: &DocFS, + dst: PathBuf, + contents: &str, + enable_minification: bool, +) -> Result<(), Error> { + if enable_minification { + if dst.extension() == Some(&OsStr::new("css")) { + let res = try_none!(minifier::css::minify(contents).ok(), &dst); + fs.write(dst, res.as_bytes()) + } else { + fs.write(dst, minifier::js::minify(contents).as_bytes()) + } + } else { + fs.write(dst, contents.as_bytes()) + } +} + +#[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, + }; + } + } +} + +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'>{}</ul>", + title, + Escape(title), + class, + e.iter().map(|s| format!("<li>{}</li>", s.print())).collect::<String>() + ); + } +} + +impl AllTypes { + fn print(self, f: &mut Buffer) { + write!( + f, + "<h1 class='fqn'>\ + <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> + <span class='in-band'>List of all items</span>\ + </h1>" + ); + 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> }, + Entry { js_data_name: &'static str, description: &'static str, default_value: bool }, +} + +impl Setting { + fn display(&self) -> String { + match *self { + Setting::Section { ref 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()).collect::<String>() + ), + Setting::Entry { ref js_data_name, ref description, ref 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, + ), + } + } +} + +impl From<(&'static str, &'static str, bool)> for Setting { + fn from(values: (&'static str, &'static str, bool)) -> Setting { + Setting::Entry { 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) -> String { + // (id, explanation, default value) + let settings: &[Setting] = &[ + ( + "Auto-hide item declarations", + vec![ + ("auto-hide-struct", "Auto-hide structs declaration", true), + ("auto-hide-enum", "Auto-hide enums declaration", false), + ("auto-hide-union", "Auto-hide unions declaration", true), + ("auto-hide-trait", "Auto-hide traits declaration", true), + ("auto-hide-macro", "Auto-hide macros declaration", false), + ], + ) + .into(), + ("auto-hide-attributes", "Auto-hide item attributes.", true).into(), + ("auto-hide-method-docs", "Auto-hide item methods' documentation", false).into(), + ("auto-hide-trait-implementations", "Auto-hide trait implementation documentation", true) + .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(), + ]; + 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()).collect::<String>(), + root_path, + suffix + ) +} + +impl Context { + fn derive_id(&self, id: String) -> String { + let mut map = self.id_map.borrow_mut(); + map.derive(id) + } + + /// String representation of how to get back to the root path of the 'doc/' + /// folder in terms of a relative URL. + fn root_path(&self) -> String { + "../".repeat(self.current.len()) + } + + fn render_item(&self, it: &clean::Item, pushname: bool, cache: &Cache) -> String { + // A little unfortunate that this is done like this, but it sure + // does make formatting *a lot* nicer. + CURRENT_DEPTH.with(|slot| { + slot.set(self.current.len()); + }); + + let mut title = if it.is_primitive() || it.is_keyword() { + // No need to include the namespace for primitive types and keywords + String::new() + } else { + self.current.join("::") + }; + if pushname { + if !title.is_empty() { + title.push_str("::"); + } + title.push_str(it.name.as_ref().unwrap()); + } + title.push_str(" - Rust"); + let tyname = it.type_(); + let desc = if it.is_crate() { + format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) + } else { + format!( + "API documentation for the Rust `{}` {} in crate `{}`.", + it.name.as_ref().unwrap(), + tyname, + self.shared.layout.krate + ) + }; + let keywords = make_item_keywords(it); + let page = layout::Page { + css_class: tyname.as_str(), + root_path: &self.root_path(), + static_root_path: self.shared.static_root_path.as_deref(), + title: &title, + description: &desc, + keywords: &keywords, + resource_suffix: &self.shared.resource_suffix, + extra_scripts: &[], + static_extra_scripts: &[], + }; + + { + self.id_map.borrow_mut().reset(); + self.id_map.borrow_mut().populate(initial_ids()); + } + + if !self.render_redirect_pages { + layout::render( + &self.shared.layout, + &page, + |buf: &mut _| print_sidebar(self, it, buf, cache), + |buf: &mut _| print_item(self, it, buf, cache), + &self.shared.style_files, + ) + } else { + let mut url = self.root_path(); + if let Some(&(ref names, ty)) = cache.paths.get(&it.def_id) { + for name in &names[..names.len() - 1] { + url.push_str(name); + url.push_str("/"); + } + url.push_str(&item_path(ty, names.last().unwrap())); + layout::redirect(&url) + } else { + String::new() + } + } + } + + fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> { + // BTreeMap instead of HashMap to get a sorted output + let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); + for item in &m.items { + if item.is_stripped() { + continue; + } + + let short = item.type_(); + let myname = match item.name { + None => continue, + Some(ref s) => s.to_string(), + }; + let short = short.to_string(); + map.entry(short) + .or_default() + .push((myname, Some(plain_summary_line(item.doc_value())))); + } + + if self.shared.sort_modules_alphabetically { + for items in map.values_mut() { + items.sort(); + } + } + map + } + + /// Generates a url appropriate for an `href` attribute back to the source of + /// this item. + /// + /// The url generated, when clicked, will redirect the browser back to the + /// original source code. + /// + /// If `None` is returned, then a source link couldn't be generated. This + /// may happen, for example, with externally inlined items where the source + /// of their crate documentation isn't known. + fn src_href(&self, item: &clean::Item, cache: &Cache) -> Option<String> { + let mut root = self.root_path(); + + let mut path = String::new(); + + // We can safely ignore synthetic `SourceFile`s. + let file = match item.source.filename { + FileName::Real(ref path) => path.local_path().to_path_buf(), + _ => return None, + }; + let file = &file; + + let (krate, path) = if item.source.cnum == LOCAL_CRATE { + if let Some(path) = self.shared.local_sources.get(file) { + (&self.shared.layout.krate, path) + } else { + return None; + } + } else { + let (krate, src_root) = match *cache.extern_locations.get(&item.source.cnum)? { + (ref name, ref src, ExternalLocation::Local) => (name, src), + (ref name, ref src, ExternalLocation::Remote(ref s)) => { + root = s.to_string(); + (name, src) + } + (_, _, ExternalLocation::Unknown) => return None, + }; + + sources::clean_path(&src_root, file, false, |component| { + path.push_str(&component.to_string_lossy()); + path.push('/'); + }); + let mut fname = file.file_name().expect("source has no filename").to_os_string(); + fname.push(".html"); + path.push_str(&fname.to_string_lossy()); + (krate, &path) + }; + + let lines = if item.source.loline == item.source.hiline { + item.source.loline.to_string() + } else { + format!("{}-{}", item.source.loline, item.source.hiline) + }; + Some(format!( + "{root}src/{krate}/{path}#{lines}", + root = Escape(&root), + krate = krate, + path = path, + lines = lines + )) + } +} + +fn wrap_into_docblock<F>(w: &mut Buffer, f: F) +where + F: FnOnce(&mut Buffer), +{ + write!(w, "<div class=\"docblock type-decl hidden-by-usual-hider\">"); + f(w); + write!(w, "</div>") +} + +fn print_item(cx: &Context, item: &clean::Item, buf: &mut Buffer, cache: &Cache) { + debug_assert!(!item.is_stripped()); + // Write the breadcrumb trail header for the top + write!(buf, "<h1 class='fqn'><span class='out-of-band'>"); + if let Some(version) = item.stable_since() { + write!( + buf, + "<span class='since' title='Stable since Rust version {0}'>{0}</span>", + version + ); + } + write!( + buf, + "<span id='render-detail'>\ + <a id=\"toggle-all-docs\" href=\"javascript:void(0)\" \ + title=\"collapse all docs\">\ + [<span class='inner'>−</span>]\ + </a>\ + </span>" + ); + + // Write `src` tag + // + // When this item is part of a `pub use` in a downstream crate, the + // [src] link in the downstream documentation will actually come back to + // this page, and this link will be auto-clicked. The `id` attribute is + // used to find the link to auto-click. + if cx.shared.include_sources && !item.is_primitive() { + if let Some(l) = cx.src_href(item, cache) { + write!(buf, "<a class='srclink' href='{}' title='{}'>[src]</a>", l, "goto source code"); + } + } + + write!(buf, "</span>"); // out-of-band + write!(buf, "<span class='in-band'>"); + let name = match item.inner { + clean::ModuleItem(ref m) => { + if m.is_crate { + "Crate " + } else { + "Module " + } + } + clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ", + clean::TraitItem(..) => "Trait ", + clean::StructItem(..) => "Struct ", + clean::UnionItem(..) => "Union ", + clean::EnumItem(..) => "Enum ", + clean::TypedefItem(..) => "Type Definition ", + clean::MacroItem(..) => "Macro ", + clean::ProcMacroItem(ref mac) => match mac.kind { + MacroKind::Bang => "Macro ", + MacroKind::Attr => "Attribute Macro ", + MacroKind::Derive => "Derive Macro ", + }, + clean::PrimitiveItem(..) => "Primitive Type ", + clean::StaticItem(..) | clean::ForeignStaticItem(..) => "Static ", + clean::ConstantItem(..) => "Constant ", + clean::ForeignTypeItem => "Foreign Type ", + clean::KeywordItem(..) => "Keyword ", + clean::OpaqueTyItem(..) => "Opaque Type ", + clean::TraitAliasItem(..) => "Trait Alias ", + _ => { + // We don't generate pages for any other type. + unreachable!(); + } + }; + buf.write_str(name); + if !item.is_primitive() && !item.is_keyword() { + let cur = &cx.current; + let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() }; + for (i, component) in cur.iter().enumerate().take(amt) { + write!( + buf, + "<a href='{}index.html'>{}</a>::<wbr>", + "../".repeat(cur.len() - i - 1), + component + ); + } + } + write!(buf, "<a class=\"{}\" href=''>{}</a>", item.type_(), item.name.as_ref().unwrap()); + + write!(buf, "</span></h1>"); // in-band + + match item.inner { + clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items), + clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => { + item_function(buf, cx, item, f) + } + clean::TraitItem(ref t) => item_trait(buf, cx, item, t, cache), + clean::StructItem(ref s) => item_struct(buf, cx, item, s, cache), + clean::UnionItem(ref s) => item_union(buf, cx, item, s, cache), + clean::EnumItem(ref e) => item_enum(buf, cx, item, e, cache), + clean::TypedefItem(ref t, _) => item_typedef(buf, cx, item, t, cache), + clean::MacroItem(ref m) => item_macro(buf, cx, item, m), + clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m), + clean::PrimitiveItem(_) => item_primitive(buf, cx, item, cache), + clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => item_static(buf, cx, item, i), + clean::ConstantItem(ref c) => item_constant(buf, cx, item, c), + clean::ForeignTypeItem => item_foreign_type(buf, cx, item, cache), + clean::KeywordItem(_) => item_keyword(buf, cx, item), + clean::OpaqueTyItem(ref e, _) => item_opaque_ty(buf, cx, item, e, cache), + clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta, cache), + _ => { + // We don't generate pages for any other type. + unreachable!(); + } + } +} + +fn item_path(ty: ItemType, name: &str) -> String { + match ty { + ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)), + _ => format!("{}.{}.html", ty, name), + } +} + +fn full_path(cx: &Context, item: &clean::Item) -> String { + let mut s = cx.current.join("::"); + s.push_str("::"); + s.push_str(item.name.as_ref().unwrap()); + s +} + +#[inline] +crate fn plain_summary_line(s: Option<&str>) -> String { + let s = s.unwrap_or(""); + // This essentially gets the first paragraph of text in one line. + let mut line = s + .lines() + .skip_while(|line| line.chars().all(|c| c.is_whitespace())) + .take_while(|line| line.chars().any(|c| !c.is_whitespace())) + .fold(String::new(), |mut acc, line| { + acc.push_str(line); + acc.push(' '); + acc + }); + // remove final whitespace + line.pop(); + markdown::plain_summary_line(&line[..]) +} + +crate fn shorten(s: String) -> String { + if s.chars().count() > 60 { + let mut len = 0; + let mut ret = s + .split_whitespace() + .take_while(|p| { + // + 1 for the added character after the word. + len += p.chars().count() + 1; + len < 60 + }) + .collect::<Vec<_>>() + .join(" "); + ret.push('…'); + ret + } else { + s + } +} + +fn document(w: &mut Buffer, cx: &Context, item: &clean::Item) { + if let Some(ref name) = item.name { + info!("Documenting {}", name); + } + document_stability(w, cx, item, false); + document_full(w, item, cx, "", false); +} + +/// Render md_text as markdown. +fn render_markdown( + w: &mut Buffer, + cx: &Context, + md_text: &str, + links: Vec<(String, String)>, + prefix: &str, + is_hidden: bool, +) { + let mut ids = cx.id_map.borrow_mut(); + write!( + w, + "<div class='docblock{}'>{}{}</div>", + if is_hidden { " hidden" } else { "" }, + prefix, + Markdown( + md_text, + &links, + &mut ids, + cx.shared.codes, + cx.shared.edition, + &cx.shared.playground + ) + .into_string() + ) +} + +fn document_short( + w: &mut Buffer, + cx: &Context, + item: &clean::Item, + link: AssocItemLink<'_>, + prefix: &str, + is_hidden: bool, +) { + if let Some(s) = item.doc_value() { + let markdown = if s.contains('\n') { + format!( + "{} [Read more]({})", + &plain_summary_line(Some(s)), + naive_assoc_href(item, link) + ) + } else { + plain_summary_line(Some(s)) + }; + render_markdown(w, cx, &markdown, item.links(), prefix, is_hidden); + } else if !prefix.is_empty() { + write!( + w, + "<div class='docblock{}'>{}</div>", + if is_hidden { " hidden" } else { "" }, + prefix + ); + } +} + +fn document_full(w: &mut Buffer, item: &clean::Item, cx: &Context, prefix: &str, is_hidden: bool) { + if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) { + debug!("Doc block: =====\n{}\n=====", s); + render_markdown(w, cx, &*s, item.links(), prefix, is_hidden); + } else if !prefix.is_empty() { + write!( + w, + "<div class='docblock{}'>{}</div>", + if is_hidden { " hidden" } else { "" }, + prefix + ); + } +} + +fn document_stability(w: &mut Buffer, cx: &Context, item: &clean::Item, is_hidden: bool) { + let stabilities = short_stability(item, cx); + if !stabilities.is_empty() { + write!(w, "<div class='stability{}'>", if is_hidden { " hidden" } else { "" }); + for stability in stabilities { + write!(w, "{}", stability); + } + write!(w, "</div>"); + } +} + +fn document_non_exhaustive_header(item: &clean::Item) -> &str { + if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" } +} + +fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) { + if item.is_non_exhaustive() { + write!(w, "<div class='docblock non-exhaustive non-exhaustive-{}'>", { + if item.is_struct() { + "struct" + } else if item.is_enum() { + "enum" + } else if item.is_variant() { + "variant" + } else { + "type" + } + }); + + if item.is_struct() { + write!( + w, + "Non-exhaustive structs could have additional fields added in future. \ + Therefore, non-exhaustive structs cannot be constructed in external crates \ + using the traditional <code>Struct {{ .. }}</code> syntax; cannot be \ + matched against without a wildcard <code>..</code>; and \ + struct update syntax will not work." + ); + } else if item.is_enum() { + write!( + w, + "Non-exhaustive enums could have additional variants added in future. \ + Therefore, when matching against variants of non-exhaustive enums, an \ + extra wildcard arm must be added to account for any future variants." + ); + } else if item.is_variant() { + write!( + w, + "Non-exhaustive enum variants could have additional fields added in future. \ + Therefore, non-exhaustive enum variants cannot be constructed in external \ + crates and cannot be matched against." + ); + } else { + write!( + w, + "This type will require a wildcard arm in any match statements or \ + constructors." + ); + } + + write!(w, "</div>"); + } +} + +fn name_key(name: &str) -> (&str, u64, usize) { + let end = name.bytes().rposition(|b| b.is_ascii_digit()).map_or(name.len(), |i| i + 1); + + // find number at end + let split = name[0..end].bytes().rposition(|b| !b.is_ascii_digit()).map_or(0, |i| i + 1); + + // count leading zeroes + let after_zeroes = + name[split..end].bytes().position(|b| b != b'0').map_or(name.len(), |extra| split + extra); + + // sort leading zeroes last + let num_zeroes = after_zeroes - split; + + match name[split..end].parse() { + Ok(n) => (&name[..split], n, num_zeroes), + Err(_) => (name, 0, num_zeroes), + } +} + +fn item_module(w: &mut Buffer, cx: &Context, item: &clean::Item, items: &[clean::Item]) { + document(w, cx, item); + + let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>(); + + // the order of item types in the listing + fn reorder(ty: ItemType) -> u8 { + match ty { + ItemType::ExternCrate => 0, + ItemType::Import => 1, + ItemType::Primitive => 2, + ItemType::Module => 3, + ItemType::Macro => 4, + ItemType::Struct => 5, + ItemType::Enum => 6, + ItemType::Constant => 7, + ItemType::Static => 8, + ItemType::Trait => 9, + ItemType::Function => 10, + ItemType::Typedef => 12, + ItemType::Union => 13, + _ => 14 + ty as u8, + } + } + + fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: usize, idx2: usize) -> Ordering { + let ty1 = i1.type_(); + let ty2 = i2.type_(); + if ty1 != ty2 { + return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)); + } + let s1 = i1.stability.as_ref().map(|s| s.level); + let s2 = i2.stability.as_ref().map(|s| s.level); + match (s1, s2) { + (Some(stability::Unstable), Some(stability::Stable)) => return Ordering::Greater, + (Some(stability::Stable), Some(stability::Unstable)) => return Ordering::Less, + _ => {} + } + let lhs = i1.name.as_ref().map_or("", |s| &**s); + let rhs = i2.name.as_ref().map_or("", |s| &**s); + name_key(lhs).cmp(&name_key(rhs)) + } + + if cx.shared.sort_modules_alphabetically { + indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2)); + } + // This call is to remove re-export duplicates in cases such as: + // + // ``` + // pub mod foo { + // pub mod bar { + // pub trait Double { fn foo(); } + // } + // } + // + // pub use foo::bar::*; + // pub use foo::*; + // ``` + // + // `Double` will appear twice in the generated docs. + // + // FIXME: This code is quite ugly and could be improved. Small issue: DefId + // can be identical even if the elements are different (mostly in imports). + // So in case this is an import, we keep everything by adding a "unique id" + // (which is the position in the vector). + indices.dedup_by_key(|i| { + ( + items[*i].def_id, + if items[*i].name.as_ref().is_some() { Some(full_path(cx, &items[*i])) } else { None }, + items[*i].type_(), + if items[*i].is_import() { *i } else { 0 }, + ) + }); + + debug!("{:?}", indices); + let mut curty = None; + for &idx in &indices { + let myitem = &items[idx]; + if myitem.is_stripped() { + continue; + } + + let myty = Some(myitem.type_()); + if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) { + // Put `extern crate` and `use` re-exports in the same section. + curty = myty; + } else if myty != curty { + if curty.is_some() { + write!(w, "</table>"); + } + curty = myty; + let (short, name) = item_ty_to_strs(&myty.unwrap()); + write!( + w, + "<h2 id='{id}' class='section-header'>\ + <a href=\"#{id}\">{name}</a></h2>\n<table>", + id = cx.derive_id(short.to_owned()), + name = name + ); + } + + match myitem.inner { + clean::ExternCrateItem(ref name, ref src) => { + use crate::html::format::anchor; + + match *src { + Some(ref src) => write!( + w, + "<tr><td><code>{}extern crate {} as {};", + myitem.visibility.print_with_space(), + anchor(myitem.def_id, src), + name + ), + None => write!( + w, + "<tr><td><code>{}extern crate {};", + myitem.visibility.print_with_space(), + anchor(myitem.def_id, name) + ), + } + write!(w, "</code></td></tr>"); + } + + clean::ImportItem(ref import) => { + write!( + w, + "<tr><td><code>{}{}</code></td></tr>", + myitem.visibility.print_with_space(), + import.print() + ); + } + + _ => { + if myitem.name.is_none() { + continue; + } + + let unsafety_flag = match myitem.inner { + clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func) + if func.header.unsafety == hir::Unsafety::Unsafe => + { + "<a title='unsafe function' href='#'><sup>âš </sup></a>" + } + _ => "", + }; + + let stab = myitem.stability_class(); + let add = if stab.is_some() { " " } else { "" }; + + let doc_value = myitem.doc_value().unwrap_or(""); + write!( + w, + "\ + <tr class='{stab}{add}module-item'>\ + <td><a class=\"{class}\" href=\"{href}\" \ + title='{title}'>{name}</a>{unsafety_flag}</td>\ + <td class='docblock-short'>{stab_tags}{docs}</td>\ + </tr>", + name = *myitem.name.as_ref().unwrap(), + stab_tags = stability_tags(myitem), + docs = MarkdownSummaryLine(doc_value, &myitem.links()).into_string(), + class = myitem.type_(), + add = add, + stab = stab.unwrap_or_else(String::new), + unsafety_flag = unsafety_flag, + href = item_path(myitem.type_(), myitem.name.as_ref().unwrap()), + title = [full_path(cx, myitem), myitem.type_().to_string()] + .iter() + .filter_map(|s| if !s.is_empty() { Some(s.as_str()) } else { None }) + .collect::<Vec<_>>() + .join(" "), + ); + } + } + } + + if curty.is_some() { + write!(w, "</table>"); + } +} + +/// Render the stability and deprecation tags that are displayed in the item's summary at the +/// module level. +fn stability_tags(item: &clean::Item) -> String { + let mut tags = String::new(); + + fn tag_html(class: &str, contents: &str) -> String { + format!(r#"<span class="stab {}">{}</span>"#, class, contents) + } + + // The trailing space after each tag is to space it properly against the rest of the docs. + if let Some(depr) = &item.deprecation { + let mut message = "Deprecated"; + if !stability::deprecation_in_effect(depr.is_since_rustc_version, depr.since.as_deref()) { + message = "Deprecation planned"; + } + tags += &tag_html("deprecated", message); + } + + // The "rustc_private" crates are permanently unstable so it makes no sense + // to render "unstable" everywhere. + if item + .stability + .as_ref() + .map(|s| s.level == stability::Unstable && s.feature.as_deref() != Some("rustc_private")) + == Some(true) + { + tags += &tag_html("unstable", "Experimental"); + } + + if let Some(ref cfg) = item.attrs.cfg { + tags += &tag_html("portability", &cfg.render_short_html()); + } + + tags +} + +/// Render the stability and/or deprecation warning that is displayed at the top of the item's +/// documentation. +fn short_stability(item: &clean::Item, cx: &Context) -> Vec<String> { + let mut stability = vec![]; + let error_codes = cx.shared.codes; + + if let Some(Deprecation { ref note, ref since, is_since_rustc_version }) = item.deprecation { + // 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 { + if !stability::deprecation_in_effect(is_since_rustc_version, Some(since)) { + format!("Deprecating in {}", Escape(&since)) + } else { + format!("Deprecated since {}", Escape(&since)) + } + } else { + String::from("Deprecated") + }; + + if let Some(note) = note { + 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())); + } + stability.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(stab) = item.stability.as_ref().filter(|stab| { + stab.level == stability::Unstable && stab.feature.as_deref() != Some("rustc_private") + }) { + let mut message = + "<span class='emoji'>🔬</span> This is a nightly-only experimental API.".to_owned(); + + if let Some(feature) = stab.feature.as_deref() { + let mut feature = format!("<code>{}</code>", Escape(&feature)); + if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, stab.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) = &stab.unstable_reason { + let mut ids = cx.id_map.borrow_mut(); + message = format!( + "<details><summary>{}</summary>{}</details>", + message, + MarkdownHtml( + &unstable_reason, + &mut ids, + error_codes, + cx.shared.edition, + &cx.shared.playground, + ) + .into_string() + ); + } + + stability.push(format!("<div class='stab unstable'>{}</div>", message)); + } + + if let Some(ref cfg) = item.attrs.cfg { + stability.push(format!("<div class='stab portability'>{}</div>", cfg.render_long_html())); + } + + stability +} + +fn item_constant(w: &mut Buffer, cx: &Context, it: &clean::Item, c: &clean::Constant) { + write!(w, "<pre class='rust const'>"); + render_attributes(w, it, false); + + write!( + w, + "{vis}const \ + {name}: {typ}", + vis = it.visibility.print_with_space(), + name = it.name.as_ref().unwrap(), + typ = c.type_.print(), + ); + + if c.value.is_some() || c.is_literal { + write!(w, " = {expr};", expr = Escape(&c.expr)); + } else { + write!(w, ";"); + } + + if let Some(value) = &c.value { + if !c.is_literal { + let value_lowercase = value.to_lowercase(); + let expr_lowercase = c.expr.to_lowercase(); + + if value_lowercase != expr_lowercase + && value_lowercase.trim_end_matches("i32") != expr_lowercase + { + write!(w, " // {value}", value = Escape(value)); + } + } + } + + write!(w, "</pre>"); + document(w, cx, it) +} + +fn item_static(w: &mut Buffer, cx: &Context, it: &clean::Item, s: &clean::Static) { + write!(w, "<pre class='rust static'>"); + render_attributes(w, it, false); + write!( + w, + "{vis}static {mutability}\ + {name}: {typ}</pre>", + vis = it.visibility.print_with_space(), + mutability = s.mutability.print_with_space(), + name = it.name.as_ref().unwrap(), + typ = s.type_.print() + ); + document(w, cx, it) +} + +fn item_function(w: &mut Buffer, cx: &Context, it: &clean::Item, f: &clean::Function) { + let header_len = format!( + "{}{}{}{}{:#}fn {}{:#}", + it.visibility.print_with_space(), + f.header.constness.print_with_space(), + f.header.asyncness.print_with_space(), + f.header.unsafety.print_with_space(), + print_abi_with_space(f.header.abi), + it.name.as_ref().unwrap(), + f.generics.print() + ) + .len(); + write!(w, "<pre class='rust fn'>"); + render_attributes(w, it, false); + write!( + w, + "{vis}{constness}{asyncness}{unsafety}{abi}fn \ + {name}{generics}{decl}{spotlight}{where_clause}</pre>", + vis = it.visibility.print_with_space(), + constness = f.header.constness.print_with_space(), + asyncness = f.header.asyncness.print_with_space(), + unsafety = f.header.unsafety.print_with_space(), + abi = print_abi_with_space(f.header.abi), + name = it.name.as_ref().unwrap(), + generics = f.generics.print(), + where_clause = WhereClause { gens: &f.generics, indent: 0, end_newline: true }, + decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness } + .print(), + spotlight = spotlight_decl(&f.decl), + ); + document(w, cx, it) +} + +fn render_implementor( + cx: &Context, + implementor: &Impl, + w: &mut Buffer, + implementor_dups: &FxHashMap<&str, (DefId, bool)>, + aliases: &[String], + cache: &Cache, +) { + // If there's already another implementor that has the same abbridged name, use the + // full path, for example in `std::iter::ExactSizeIterator` + let use_absolute = match implementor.inner_impl().for_ { + clean::ResolvedPath { ref path, is_generic: false, .. } + | clean::BorrowedRef { + type_: box clean::ResolvedPath { ref path, is_generic: false, .. }, + .. + } => implementor_dups[path.last_name()].1, + _ => false, + }; + render_impl( + w, + cx, + implementor, + AssocItemLink::Anchor(None), + RenderMode::Normal, + implementor.impl_item.stable_since(), + false, + Some(use_absolute), + false, + false, + aliases, + cache, + ); +} + +fn render_impls( + cx: &Context, + w: &mut Buffer, + traits: &[&&Impl], + containing_item: &clean::Item, + cache: &Cache, +) { + let mut impls = traits + .iter() + .map(|i| { + let did = i.trait_did().unwrap(); + let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods); + let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() }; + render_impl( + &mut buffer, + cx, + i, + assoc_link, + RenderMode::Normal, + containing_item.stable_since(), + true, + None, + false, + true, + &[], + cache, + ); + buffer.into_inner() + }) + .collect::<Vec<_>>(); + impls.sort(); + w.write_str(&impls.join("")); +} + +fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool) -> String { + let mut bounds = String::new(); + if !t_bounds.is_empty() { + if !trait_alias { + bounds.push_str(": "); + } + for (i, p) in t_bounds.iter().enumerate() { + if i > 0 { + bounds.push_str(" + "); + } + bounds.push_str(&p.print().to_string()); + } + } + bounds +} + +fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl) -> Ordering { + let lhs = format!("{}", lhs.inner_impl().print()); + let rhs = format!("{}", rhs.inner_impl().print()); + + // lhs and rhs are formatted as HTML, which may be unnecessary + name_key(&lhs).cmp(&name_key(&rhs)) +} + +fn item_trait(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Trait, cache: &Cache) { + let bounds = bounds(&t.bounds, false); + let types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>(); + let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::<Vec<_>>(); + let required = t.items.iter().filter(|m| m.is_ty_method()).collect::<Vec<_>>(); + let provided = t.items.iter().filter(|m| m.is_method()).collect::<Vec<_>>(); + + // Output the trait definition + wrap_into_docblock(w, |w| { + write!(w, "<pre class='rust trait'>"); + render_attributes(w, it, true); + write!( + w, + "{}{}{}trait {}{}{}", + it.visibility.print_with_space(), + t.unsafety.print_with_space(), + if t.is_auto { "auto " } else { "" }, + it.name.as_ref().unwrap(), + t.generics.print(), + bounds + ); + + if !t.generics.where_predicates.is_empty() { + write!(w, "{}", WhereClause { gens: &t.generics, indent: 0, end_newline: true }); + } else { + write!(w, " "); + } + + if t.items.is_empty() { + write!(w, "{{ }}"); + } else { + // FIXME: we should be using a derived_id for the Anchors here + write!(w, "{{\n"); + for t in &types { + render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait); + write!(w, ";\n"); + } + if !types.is_empty() && !consts.is_empty() { + w.write_str("\n"); + } + for t in &consts { + render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait); + write!(w, ";\n"); + } + if !consts.is_empty() && !required.is_empty() { + w.write_str("\n"); + } + for (pos, m) in required.iter().enumerate() { + render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait); + write!(w, ";\n"); + + if pos < required.len() - 1 { + write!(w, "<div class='item-spacer'></div>"); + } + } + if !required.is_empty() && !provided.is_empty() { + w.write_str("\n"); + } + for (pos, m) in provided.iter().enumerate() { + render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait); + match m.inner { + clean::MethodItem(ref inner) if !inner.generics.where_predicates.is_empty() => { + write!(w, ",\n {{ ... }}\n"); + } + _ => { + write!(w, " {{ ... }}\n"); + } + } + if pos < provided.len() - 1 { + write!(w, "<div class='item-spacer'></div>"); + } + } + write!(w, "}}"); + } + write!(w, "</pre>") + }); + + // Trait documentation + document(w, cx, it); + + fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { + write!( + w, + " + <h2 id='{0}' class='small-section-header'>\ + {1}<a href='#{0}' class='anchor'></a>\ + </h2>{2}", + id, title, extra_content + ) + } + + fn write_loading_content(w: &mut Buffer, extra_content: &str) { + write!(w, "{}<span class='loading-content'>Loading content...</span>", extra_content) + } + + fn trait_item(w: &mut Buffer, cx: &Context, m: &clean::Item, t: &clean::Item) { + let name = m.name.as_ref().unwrap(); + let item_type = m.type_(); + let id = cx.derive_id(format!("{}.{}", item_type, name)); + write!(w, "<h3 id='{id}' class='method'><code>", id = id,); + render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl); + write!(w, "</code>"); + render_stability_since(w, m, t); + write!(w, "</h3>"); + document(w, cx, m); + } + + if !types.is_empty() { + write_small_section_header( + w, + "associated-types", + "Associated Types", + "<div class='methods'>", + ); + for t in &types { + trait_item(w, cx, *t, it); + } + write_loading_content(w, "</div>"); + } + + if !consts.is_empty() { + write_small_section_header( + w, + "associated-const", + "Associated Constants", + "<div class='methods'>", + ); + for t in &consts { + trait_item(w, cx, *t, it); + } + write_loading_content(w, "</div>"); + } + + // Output the documentation for each function individually + if !required.is_empty() { + write_small_section_header( + w, + "required-methods", + "Required methods", + "<div class='methods'>", + ); + for m in &required { + trait_item(w, cx, *m, it); + } + write_loading_content(w, "</div>"); + } + if !provided.is_empty() { + write_small_section_header( + w, + "provided-methods", + "Provided methods", + "<div class='methods'>", + ); + for m in &provided { + trait_item(w, cx, *m, it); + } + write_loading_content(w, "</div>"); + } + + // If there are methods directly on this trait object, render them here. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache); + + if let Some(implementors) = cache.implementors.get(&it.def_id) { + // The DefId is for the first Type found with that name. The bool is + // if any Types with the same name but different DefId have been found. + let mut implementor_dups: FxHashMap<&str, (DefId, bool)> = FxHashMap::default(); + for implementor in implementors { + match implementor.inner_impl().for_ { + clean::ResolvedPath { ref path, did, is_generic: false, .. } + | clean::BorrowedRef { + type_: box clean::ResolvedPath { ref path, did, is_generic: false, .. }, + .. + } => { + let &mut (prev_did, ref mut has_duplicates) = + implementor_dups.entry(path.last_name()).or_insert((did, false)); + if prev_did != did { + *has_duplicates = true; + } + } + _ => {} + } + } + + let (local, foreign) = implementors.iter().partition::<Vec<_>, _>(|i| { + i.inner_impl().for_.def_id().map_or(true, |d| cache.paths.contains_key(&d)) + }); + + let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) = + local.iter().partition(|i| i.inner_impl().synthetic); + + synthetic.sort_by(compare_impl); + concrete.sort_by(compare_impl); + + if !foreign.is_empty() { + write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", ""); + + for implementor in foreign { + let assoc_link = AssocItemLink::GotoSource( + implementor.impl_item.def_id, + &implementor.inner_impl().provided_trait_methods, + ); + render_impl( + w, + cx, + &implementor, + assoc_link, + RenderMode::Normal, + implementor.impl_item.stable_since(), + false, + None, + true, + false, + &[], + cache, + ); + } + write_loading_content(w, ""); + } + + write_small_section_header( + w, + "implementors", + "Implementors", + "<div class='item-list' id='implementors-list'>", + ); + for implementor in concrete { + render_implementor(cx, implementor, w, &implementor_dups, &[], cache); + } + write_loading_content(w, "</div>"); + + if t.auto { + write_small_section_header( + w, + "synthetic-implementors", + "Auto implementors", + "<div class='item-list' id='synthetic-implementors-list'>", + ); + for implementor in synthetic { + render_implementor( + cx, + implementor, + w, + &implementor_dups, + &collect_paths_for_type(implementor.inner_impl().for_.clone()), + cache, + ); + } + write_loading_content(w, "</div>"); + } + } else { + // even without any implementations to write in, we still want the heading and list, so the + // implementors javascript file pulled in below has somewhere to write the impls into + write_small_section_header( + w, + "implementors", + "Implementors", + "<div class='item-list' id='implementors-list'>", + ); + write_loading_content(w, "</div>"); + + if t.auto { + write_small_section_header( + w, + "synthetic-implementors", + "Auto implementors", + "<div class='item-list' id='synthetic-implementors-list'>", + ); + write_loading_content(w, "</div>"); + } + } + + write!( + w, + "<script type=\"text/javascript\" \ + src=\"{root_path}/implementors/{path}/{ty}.{name}.js\" async>\ + </script>", + root_path = vec![".."; cx.current.len()].join("/"), + path = if it.def_id.is_local() { + cx.current.join("/") + } else { + let (ref path, _) = cache.external_paths[&it.def_id]; + path[..path.len() - 1].join("/") + }, + ty = it.type_(), + name = *it.name.as_ref().unwrap() + ); +} + +fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>) -> 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).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, +) { + write!( + w, + "{}{}const <a href='{}' class=\"constant\"><b>{}</b></a>: {}", + extra, + it.visibility.print_with_space(), + naive_assoc_href(it, link), + it.name.as_ref().unwrap(), + ty.print() + ); +} + +fn assoc_type( + w: &mut Buffer, + it: &clean::Item, + bounds: &[clean::GenericBound], + default: Option<&clean::Type>, + link: AssocItemLink<'_>, + extra: &str, +) { + write!( + w, + "{}type <a href='{}' class=\"type\">{}</a>", + extra, + naive_assoc_href(it, link), + it.name.as_ref().unwrap() + ); + if !bounds.is_empty() { + write!(w, ": {}", print_generic_bounds(bounds)) + } + if let Some(default) = default { + write!(w, " = {}", default.print()) + } +} + +fn render_stability_since_raw(w: &mut Buffer, ver: Option<&str>, containing_ver: Option<&str>) { + if let Some(v) = ver { + if containing_ver != ver && !v.is_empty() { + write!(w, "<span class='since' title='Stable since Rust version {0}'>{0}</span>", v) + } + } +} + +fn render_stability_since(w: &mut Buffer, item: &clean::Item, containing_item: &clean::Item) { + render_stability_since_raw(w, item.stable_since(), containing_item.stable_since()) +} + +fn render_assoc_item( + w: &mut Buffer, + item: &clean::Item, + link: AssocItemLink<'_>, + parent: ItemType, +) { + fn method( + w: &mut Buffer, + meth: &clean::Item, + header: hir::FnHeader, + g: &clean::Generics, + d: &clean::FnDecl, + link: AssocItemLink<'_>, + parent: ItemType, + ) { + let name = meth.name.as_ref().unwrap(); + let anchor = format!("#{}.{}", meth.type_(), name); + let href = match link { + AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), + AssocItemLink::Anchor(None) => anchor, + 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).map(|p| format!("{}#{}.{}", p.0, ty, name)).unwrap_or(anchor) + } + }; + let mut header_len = format!( + "{}{}{}{}{}{:#}fn {}{:#}", + meth.visibility.print_with_space(), + header.constness.print_with_space(), + header.asyncness.print_with_space(), + header.unsafety.print_with_space(), + print_default_space(meth.is_default()), + print_abi_with_space(header.abi), + name, + g.print() + ) + .len(); + let (indent, end_newline) = if parent == ItemType::Trait { + header_len += 4; + (4, false) + } else { + (0, true) + }; + render_attributes(w, meth, false); + write!( + w, + "{}{}{}{}{}{}{}fn <a href='{href}' class='fnname'>{name}</a>\ + {generics}{decl}{spotlight}{where_clause}", + if parent == ItemType::Trait { " " } else { "" }, + meth.visibility.print_with_space(), + header.constness.print_with_space(), + header.asyncness.print_with_space(), + header.unsafety.print_with_space(), + print_default_space(meth.is_default()), + print_abi_with_space(header.abi), + href = href, + name = name, + generics = g.print(), + decl = Function { decl: d, header_len, indent, asyncness: header.asyncness }.print(), + spotlight = spotlight_decl(&d), + where_clause = WhereClause { gens: g, indent, end_newline } + ) + } + match item.inner { + clean::StrippedItem(..) => {} + clean::TyMethodItem(ref m) => method(w, item, m.header, &m.generics, &m.decl, link, parent), + clean::MethodItem(ref m) => method(w, item, m.header, &m.generics, &m.decl, link, parent), + clean::AssocConstItem(ref ty, ref default) => assoc_const( + w, + item, + ty, + default.as_ref(), + link, + if parent == ItemType::Trait { " " } else { "" }, + ), + clean::AssocTypeItem(ref bounds, ref default) => assoc_type( + w, + item, + bounds, + default.as_ref(), + link, + if parent == ItemType::Trait { " " } else { "" }, + ), + _ => panic!("render_assoc_item called on non-associated-item"), + } +} + +fn item_struct(w: &mut Buffer, cx: &Context, it: &clean::Item, s: &clean::Struct, cache: &Cache) { + wrap_into_docblock(w, |w| { + write!(w, "<pre class='rust struct'>"); + render_attributes(w, it, true); + render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true); + write!(w, "</pre>") + }); + + document(w, cx, it); + let mut fields = s + .fields + .iter() + .filter_map(|f| match f.inner { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable(); + if let doctree::Plain = s.struct_type { + if fields.peek().is_some() { + write!( + w, + "<h2 id='fields' class='fields small-section-header'> + Fields{}<a href='#fields' class='anchor'></a></h2>", + document_non_exhaustive_header(it) + ); + document_non_exhaustive(w, it); + for (field, ty) in fields { + let id = cx.derive_id(format!( + "{}.{}", + ItemType::StructField, + field.name.as_ref().unwrap() + )); + write!( + w, + "<span id=\"{id}\" class=\"{item_type} small-section-header\">\ + <a href=\"#{id}\" class=\"anchor field\"></a>\ + <code>{name}: {ty}</code>\ + </span>", + item_type = ItemType::StructField, + id = id, + name = field.name.as_ref().unwrap(), + ty = ty.print() + ); + document(w, cx, field); + } + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +fn item_union(w: &mut Buffer, cx: &Context, it: &clean::Item, s: &clean::Union, cache: &Cache) { + wrap_into_docblock(w, |w| { + write!(w, "<pre class='rust union'>"); + render_attributes(w, it, true); + render_union(w, it, Some(&s.generics), &s.fields, "", true); + write!(w, "</pre>") + }); + + document(w, cx, it); + let mut fields = s + .fields + .iter() + .filter_map(|f| match f.inner { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable(); + if fields.peek().is_some() { + write!( + w, + "<h2 id='fields' class='fields small-section-header'> + Fields<a href='#fields' class='anchor'></a></h2>" + ); + for (field, ty) in fields { + let name = field.name.as_ref().expect("union field name"); + let id = format!("{}.{}", ItemType::StructField, name); + write!( + w, + "<span id=\"{id}\" class=\"{shortty} small-section-header\">\ + <a href=\"#{id}\" class=\"anchor field\"></a>\ + <code>{name}: {ty}</code>\ + </span>", + id = id, + name = name, + shortty = ItemType::StructField, + ty = ty.print() + ); + if let Some(stability_class) = field.stability_class() { + write!(w, "<span class='stab {stab}'></span>", stab = stability_class); + } + document(w, cx, field); + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +fn item_enum(w: &mut Buffer, cx: &Context, it: &clean::Item, e: &clean::Enum, cache: &Cache) { + wrap_into_docblock(w, |w| { + write!(w, "<pre class='rust enum'>"); + render_attributes(w, it, true); + write!( + w, + "{}enum {}{}{}", + it.visibility.print_with_space(), + it.name.as_ref().unwrap(), + e.generics.print(), + WhereClause { gens: &e.generics, indent: 0, end_newline: true } + ); + if e.variants.is_empty() && !e.variants_stripped { + write!(w, " {{}}"); + } else { + write!(w, " {{\n"); + for v in &e.variants { + write!(w, " "); + let name = v.name.as_ref().unwrap(); + match v.inner { + clean::VariantItem(ref var) => match var.kind { + clean::VariantKind::CLike => write!(w, "{}", name), + clean::VariantKind::Tuple(ref tys) => { + write!(w, "{}(", name); + for (i, ty) in tys.iter().enumerate() { + if i > 0 { + write!(w, ", ") + } + write!(w, "{}", ty.print()); + } + write!(w, ")"); + } + clean::VariantKind::Struct(ref s) => { + render_struct(w, v, None, s.struct_type, &s.fields, " ", false); + } + }, + _ => unreachable!(), + } + write!(w, ",\n"); + } + + if e.variants_stripped { + write!(w, " // some variants omitted\n"); + } + write!(w, "}}"); + } + write!(w, "</pre>") + }); + + document(w, cx, it); + if !e.variants.is_empty() { + write!( + w, + "<h2 id='variants' class='variants small-section-header'> + Variants{}<a href='#variants' class='anchor'></a></h2>\n", + document_non_exhaustive_header(it) + ); + document_non_exhaustive(w, it); + for variant in &e.variants { + let id = + cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.as_ref().unwrap())); + write!( + w, + "<div id=\"{id}\" class=\"variant small-section-header\">\ + <a href=\"#{id}\" class=\"anchor field\"></a>\ + <code>{name}", + id = id, + name = variant.name.as_ref().unwrap() + ); + if let clean::VariantItem(ref var) = variant.inner { + if let clean::VariantKind::Tuple(ref tys) = var.kind { + write!(w, "("); + for (i, ty) in tys.iter().enumerate() { + if i > 0 { + write!(w, ", "); + } + write!(w, "{}", ty.print()); + } + write!(w, ")"); + } + } + write!(w, "</code></div>"); + document(w, cx, variant); + document_non_exhaustive(w, variant); + + use crate::clean::{Variant, VariantKind}; + if let clean::VariantItem(Variant { kind: VariantKind::Struct(ref s) }) = variant.inner + { + let variant_id = cx.derive_id(format!( + "{}.{}.fields", + ItemType::Variant, + variant.name.as_ref().unwrap() + )); + write!(w, "<div class='autohide sub-variant' id='{id}'>", id = variant_id); + write!( + w, + "<h3>Fields of <b>{name}</b></h3><div>", + name = variant.name.as_ref().unwrap() + ); + for field in &s.fields { + use crate::clean::StructFieldItem; + if let StructFieldItem(ref ty) = field.inner { + let id = cx.derive_id(format!( + "variant.{}.field.{}", + variant.name.as_ref().unwrap(), + field.name.as_ref().unwrap() + )); + write!( + w, + "<span id=\"{id}\" class=\"variant small-section-header\">\ + <a href=\"#{id}\" class=\"anchor field\"></a>\ + <code>{f}: {t}\ + </code></span>", + id = id, + f = field.name.as_ref().unwrap(), + t = ty.print() + ); + document(w, cx, field); + } + } + write!(w, "</div></div>"); + } + render_stability_since(w, variant, it); + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +const ALLOWED_ATTRIBUTES: &[Symbol] = &[ + sym::export_name, + sym::lang, + sym::link_section, + sym::must_use, + sym::no_mangle, + sym::repr, + sym::non_exhaustive, +]; + +// The `top` parameter is used when generating the item declaration to ensure it doesn't have a +// left padding. For example: +// +// #[foo] <----- "top" attribute +// struct Foo { +// #[bar] <---- not "top" attribute +// bar: usize, +// } +fn render_attributes(w: &mut Buffer, it: &clean::Item, top: bool) { + let attrs = it + .attrs + .other_attrs + .iter() + .filter_map(|attr| { + if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) { + Some(pprust::attribute_to_string(&attr)) + } else { + None + } + }) + .join("\n"); + + if !attrs.is_empty() { + write!( + w, + "<span class=\"docblock attributes{}\">{}</span>", + if top { " top-attr" } else { "" }, + &attrs + ); + } +} + +fn render_struct( + w: &mut Buffer, + it: &clean::Item, + g: Option<&clean::Generics>, + ty: doctree::StructType, + fields: &[clean::Item], + tab: &str, + structhead: bool, +) { + write!( + w, + "{}{}{}", + it.visibility.print_with_space(), + if structhead { "struct " } else { "" }, + it.name.as_ref().unwrap() + ); + if let Some(g) = g { + write!(w, "{}", g.print()) + } + match ty { + doctree::Plain => { + if let Some(g) = g { + write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }) + } + let mut has_visible_fields = false; + write!(w, " {{"); + for field in fields { + if let clean::StructFieldItem(ref ty) = field.inner { + write!( + w, + "\n{} {}{}: {},", + tab, + field.visibility.print_with_space(), + field.name.as_ref().unwrap(), + ty.print() + ); + has_visible_fields = true; + } + } + + if has_visible_fields { + if it.has_stripped_fields().unwrap() { + write!(w, "\n{} // some fields omitted", tab); + } + write!(w, "\n{}", tab); + } else if it.has_stripped_fields().unwrap() { + // If there are no visible fields we can just display + // `{ /* fields omitted */ }` to save space. + write!(w, " /* fields omitted */ "); + } + write!(w, "}}"); + } + doctree::Tuple => { + write!(w, "("); + for (i, field) in fields.iter().enumerate() { + if i > 0 { + write!(w, ", "); + } + match field.inner { + clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), + clean::StructFieldItem(ref ty) => { + write!(w, "{}{}", field.visibility.print_with_space(), ty.print()) + } + _ => unreachable!(), + } + } + write!(w, ")"); + if let Some(g) = g { + write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: false }) + } + write!(w, ";"); + } + doctree::Unit => { + // Needed for PhantomData. + if let Some(g) = g { + write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: false }) + } + write!(w, ";"); + } + } +} + +fn render_union( + w: &mut Buffer, + it: &clean::Item, + g: Option<&clean::Generics>, + fields: &[clean::Item], + tab: &str, + structhead: bool, +) { + write!( + w, + "{}{}{}", + it.visibility.print_with_space(), + if structhead { "union " } else { "" }, + it.name.as_ref().unwrap() + ); + if let Some(g) = g { + write!(w, "{}", g.print()); + write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }); + } + + write!(w, " {{\n{}", tab); + for field in fields { + if let clean::StructFieldItem(ref ty) = field.inner { + write!( + w, + " {}{}: {},\n{}", + field.visibility.print_with_space(), + field.name.as_ref().unwrap(), + ty.print(), + tab + ); + } + } + + if it.has_stripped_fields().unwrap() { + write!(w, " // some fields omitted\n{}", tab); + } + write!(w, "}}"); +} + +#[derive(Copy, Clone)] +enum AssocItemLink<'a> { + Anchor(Option<&'a str>), + GotoSource(DefId, &'a FxHashSet<String>), +} + +impl<'a> AssocItemLink<'a> { + fn anchor(&self, id: &'a String) -> 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<'_>, + cache: &Cache, +) { + let v = match cache.impls.get(&it) { + Some(v) => v, + None => return, + }; + 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 => { + write!( + w, + "\ + <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 {}<Target = {}>\ + <a href='#deref-methods' class='anchor'></a>\ + </h2>\ + ", + trait_.print(), + type_.print() + ); + RenderMode::ForDeref { mut_: deref_mut_ } + } + }; + for i in &non_trait { + render_impl( + w, + cx, + i, + AssocItemLink::Anchor(None), + render_mode, + containing_item.stable_since(), + true, + None, + false, + true, + &[], + cache, + ); + } + } + if let AssocItemRender::DerefFor { .. } = what { + return; + } + if !traits.is_empty() { + let deref_impl = + traits.iter().find(|t| t.inner_impl().trait_.def_id() == cache.deref_trait_did); + if let Some(impl_) = deref_impl { + let has_deref_mut = + traits.iter().any(|t| t.inner_impl().trait_.def_id() == cache.deref_mut_trait_did); + render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, cache); + } + + 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, cache); + 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() { + write!( + w, + "\ + <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, cache); + write!(w, "</div>"); + } + + if !blanket_impl.is_empty() { + write!( + w, + "\ + <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, cache); + write!(w, "</div>"); + } + } +} + +fn render_deref_methods( + w: &mut Buffer, + cx: &Context, + impl_: &Impl, + container_item: &clean::Item, + deref_mut: bool, + cache: &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.inner { + 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"); + let what = + AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut }; + if let Some(did) = target.def_id() { + render_assoc_items(w, cx, container_item, did, what, cache); + } else { + if let Some(prim) = target.primitive_type() { + if let Some(&did) = cache.primitive_locations.get(&prim) { + render_assoc_items(w, cx, container_item, did, what, cache); + } + } + } +} + +fn should_render_item(item: &clean::Item, deref_mut_: bool) -> bool { + let self_type_opt = match item.inner { + 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 spotlight_decl(decl: &clean::FnDecl) -> String { + let mut out = Buffer::html(); + let mut trait_ = String::new(); + + if let Some(did) = decl.output.def_id() { + let c = cache(); + if let Some(impls) = c.impls.get(&did) { + for i in impls { + let impl_ = i.inner_impl(); + if impl_.trait_.def_id().map_or(false, |d| c.traits[&d].is_spotlight) { + if out.is_empty() { + out.push_str(&format!( + "<h3 class=\"important\">Important traits for {}</h3>\ + <code class=\"content\">", + impl_.for_.print() + )); + trait_.push_str(&impl_.for_.print().to_string()); + } + + //use the "where" class here to make it small + out.push_str(&format!( + "<span class=\"where fmt-newline\">{}</span>", + impl_.print() + )); + let t_did = impl_.trait_.def_id().unwrap(); + for it in &impl_.items { + if let clean::TypedefItem(ref tydef, _) = it.inner { + out.push_str("<span class=\"where fmt-newline\"> "); + assoc_type( + &mut out, + it, + &[], + Some(&tydef.type_), + AssocItemLink::GotoSource(t_did, &FxHashSet::default()), + "", + ); + out.push_str(";</span>"); + } + } + } + } + } + } + + if !out.is_empty() { + out.insert_str( + 0, + "<span class=\"important-traits\"><span class=\"important-traits-tooltip\">ⓘ<div class='important-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, + link: AssocItemLink<'_>, + render_mode: RenderMode, + outer_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], + cache: &Cache, +) { + if render_mode == RenderMode::Normal { + 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) + } else { + format!("impl-{}", small_url_encode(&format!("{:#}", t.print()))) + } + } + None => "impl".to_string(), + }); + let aliases = if aliases.is_empty() { + String::new() + } else { + format!(" aliases=\"{}\"", aliases.join(",")) + }; + if let Some(use_absolute) = use_absolute { + write!(w, "<h3 id='{}' class='impl'{}><code class='in-band'>", id, aliases); + fmt_impl_for_trait_page(&i.inner_impl(), w, use_absolute); + if show_def_docs { + for it in &i.inner_impl().items { + if let clean::TypedefItem(ref tydef, _) = it.inner { + write!(w, "<span class=\"where fmt-newline\"> "); + assoc_type(w, it, &[], Some(&tydef.type_), AssocItemLink::Anchor(None), ""); + write!(w, ";</span>"); + } + } + } + write!(w, "</code>"); + } else { + write!( + w, + "<h3 id='{}' class='impl'{}><code class='in-band'>{}</code>", + id, + aliases, + i.inner_impl().print() + ); + } + write!(w, "<a href='#{}' class='anchor'></a>", id); + let since = i.impl_item.stability.as_ref().map(|s| &s.since[..]); + render_stability_since_raw(w, since, outer_version); + if let Some(l) = cx.src_href(&i.impl_item, cache) { + write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>", l, "goto source code"); + } + write!(w, "</h3>"); + 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(), + &mut ids, + cx.shared.codes, + cx.shared.edition, + &cx.shared.playground + ) + .into_string() + ); + } + } + + fn doc_impl_item( + w: &mut Buffer, + cx: &Context, + item: &clean::Item, + link: AssocItemLink<'_>, + render_mode: RenderMode, + is_default_item: bool, + outer_version: Option<&str>, + trait_: Option<&clean::Trait>, + show_def_docs: bool, + cache: &Cache, + ) { + let item_type = item.type_(); + let name = item.name.as_ref().unwrap(); + + let render_method_item = match render_mode { + RenderMode::Normal => true, + RenderMode::ForDeref { mut_: deref_mut_ } => should_render_item(&item, deref_mut_), + }; + + let (is_hidden, extra_class) = + if (trait_.is_none() || item.doc_value().is_some() || item.inner.is_associated()) + && !is_default_item + { + (false, "") + } else { + (true, " hidden") + }; + match item.inner { + clean::MethodItem(clean::Method { .. }) + | clean::TyMethodItem(clean::TyMethod { .. }) => { + // 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)); + write!(w, "<h4 id='{}' class=\"{}{}\">", id, item_type, extra_class); + write!(w, "<code>"); + render_assoc_item(w, item, link.anchor(&id), ItemType::Impl); + write!(w, "</code>"); + render_stability_since_raw(w, item.stable_since(), outer_version); + if let Some(l) = cx.src_href(item, cache) { + write!( + w, + "<a class='srclink' href='{}' title='{}'>[src]</a>", + l, "goto source code" + ); + } + write!(w, "</h4>"); + } + } + clean::TypedefItem(ref tydef, _) => { + let id = cx.derive_id(format!("{}.{}", ItemType::AssocType, name)); + write!(w, "<h4 id='{}' class=\"{}{}\"><code>", id, item_type, extra_class); + assoc_type(w, item, &Vec::new(), Some(&tydef.type_), link.anchor(&id), ""); + write!(w, "</code></h4>"); + } + clean::AssocConstItem(ref ty, ref default) => { + let id = cx.derive_id(format!("{}.{}", item_type, name)); + write!(w, "<h4 id='{}' class=\"{}{}\"><code>", id, item_type, extra_class); + assoc_const(w, item, ty, default.as_ref(), link.anchor(&id), ""); + write!(w, "</code>"); + render_stability_since_raw(w, item.stable_since(), outer_version); + if let Some(l) = cx.src_href(item, cache) { + write!( + w, + "<a class='srclink' href='{}' title='{}'>[src]</a>", + l, "goto source code" + ); + } + write!(w, "</h4>"); + } + clean::AssocTypeItem(ref bounds, ref default) => { + let id = cx.derive_id(format!("{}.{}", item_type, name)); + write!(w, "<h4 id='{}' class=\"{}{}\"><code>", id, item_type, extra_class); + assoc_type(w, item, bounds, default.as_ref(), link.anchor(&id), ""); + write!(w, "</code></h4>"); + } + clean::StrippedItem(..) => return, + _ => panic!("can't make docs for trait item with name {:?}", item.name), + } + + 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. + document_stability(w, cx, it, is_hidden); + if item.doc_value().is_some() { + document_full(w, item, cx, "", is_hidden); + } else if show_def_docs { + // In case the item isn't documented, + // provide short documentation from the trait. + document_short(w, cx, it, link, "", is_hidden); + } + } + } else { + document_stability(w, cx, item, is_hidden); + if show_def_docs { + document_full(w, item, cx, "", is_hidden); + } + } + } else { + document_stability(w, cx, item, is_hidden); + if show_def_docs { + document_short(w, cx, item, link, "", is_hidden); + } + } + } + } + + let traits = &cache.traits; + let trait_ = i.trait_did().map(|did| &traits[&did]); + + write!(w, "<div class='impl-items'>"); + for trait_item in &i.inner_impl().items { + doc_impl_item( + w, + cx, + trait_item, + link, + render_mode, + false, + outer_version, + trait_, + show_def_docs, + cache, + ); + } + + fn render_default_items( + w: &mut Buffer, + cx: &Context, + t: &clean::Trait, + i: &clean::Impl, + render_mode: RenderMode, + outer_version: Option<&str>, + show_def_docs: bool, + cache: &Cache, + ) { + for trait_item in &t.items { + let n = trait_item.name.clone(); + if i.items.iter().any(|m| m.name == n) { + continue; + } + let did = i.trait_.as_ref().unwrap().def_id().unwrap(); + let assoc_link = AssocItemLink::GotoSource(did, &i.provided_trait_methods); + + doc_impl_item( + w, + cx, + trait_item, + assoc_link, + render_mode, + true, + outer_version, + None, + show_def_docs, + cache, + ); + } + } + + // 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( + w, + cx, + t, + &i.inner_impl(), + render_mode, + outer_version, + show_def_docs, + cache, + ); + } + } + write!(w, "</div>"); +} + +fn item_opaque_ty( + w: &mut Buffer, + cx: &Context, + it: &clean::Item, + t: &clean::OpaqueTy, + cache: &Cache, +) { + write!(w, "<pre class='rust opaque'>"); + render_attributes(w, it, false); + write!( + w, + "type {}{}{where_clause} = impl {bounds};</pre>", + it.name.as_ref().unwrap(), + t.generics.print(), + where_clause = WhereClause { gens: &t.generics, indent: 0, end_newline: true }, + bounds = bounds(&t.bounds, false) + ); + + document(w, cx, it); + + // Render any items associated directly to this alias, as otherwise they + // won't be visible anywhere in the docs. It would be nice to also show + // associated items from the aliased type (see discussion in #32077), but + // we need #14072 to make sense of the generics. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +fn item_trait_alias( + w: &mut Buffer, + cx: &Context, + it: &clean::Item, + t: &clean::TraitAlias, + cache: &Cache, +) { + write!(w, "<pre class='rust trait-alias'>"); + render_attributes(w, it, false); + write!( + w, + "trait {}{}{} = {};</pre>", + it.name.as_ref().unwrap(), + t.generics.print(), + WhereClause { gens: &t.generics, indent: 0, end_newline: true }, + bounds(&t.bounds, true) + ); + + document(w, cx, it); + + // Render any items associated directly to this alias, as otherwise they + // won't be visible anywhere in the docs. It would be nice to also show + // associated items from the aliased type (see discussion in #32077), but + // we need #14072 to make sense of the generics. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +fn item_typedef(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Typedef, cache: &Cache) { + write!(w, "<pre class='rust typedef'>"); + render_attributes(w, it, false); + write!( + w, + "type {}{}{where_clause} = {type_};</pre>", + it.name.as_ref().unwrap(), + t.generics.print(), + where_clause = WhereClause { gens: &t.generics, indent: 0, end_newline: true }, + type_ = t.type_.print() + ); + + document(w, cx, it); + + // Render any items associated directly to this alias, as otherwise they + // won't be visible anywhere in the docs. It would be nice to also show + // associated items from the aliased type (see discussion in #32077), but + // we need #14072 to make sense of the generics. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +fn item_foreign_type(w: &mut Buffer, cx: &Context, it: &clean::Item, cache: &Cache) { + writeln!(w, "<pre class='rust foreigntype'>extern {{"); + render_attributes(w, it, false); + write!( + w, + " {}type {};\n}}</pre>", + it.visibility.print_with_space(), + it.name.as_ref().unwrap(), + ); + + document(w, cx, it); + + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +fn print_sidebar(cx: &Context, it: &clean::Item, buffer: &mut Buffer, cache: &Cache) { + 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.inner { + 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) = cache.crate_version { + write!( + buffer, + "<div class='block version'>\ + <p>Version {}</p>\ + </div>", + Escape(version) + ); + } + } + + write!(buffer, "<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.inner { + clean::StructItem(ref s) => sidebar_struct(buffer, it, s), + clean::TraitItem(ref t) => sidebar_trait(buffer, it, t), + clean::PrimitiveItem(_) => sidebar_primitive(buffer, it), + clean::UnionItem(ref u) => sidebar_union(buffer, it, u), + clean::EnumItem(ref e) => sidebar_enum(buffer, it, e), + clean::TypedefItem(_, _) => sidebar_typedef(buffer, it), + clean::ModuleItem(ref m) => sidebar_module(buffer, &m.items), + clean::ForeignTypeItem => sidebar_foreign_type(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). + + write!(buffer, "<p class='location'>"); + for (i, name) in cx.current.iter().take(parentlen).enumerate() { + if i > 0 { + write!(buffer, "::<wbr>"); + } + write!( + buffer, + "<a href='{}index.html'>{}</a>", + &cx.root_path()[..(cx.current.len() - i - 1) * 3], + *name + ); + } + write!(buffer, "</p>"); + + // Sidebar refers to the enclosing module, not this module. + let relpath = if it.is_mod() { "../" } else { "" }; + write!( + buffer, + "<script>window.sidebarCurrent = {{\ + name: '{name}', \ + ty: '{ty}', \ + relpath: '{path}'\ + }};</script>", + name = it.name.as_ref().map(|x| &x[..]).unwrap_or(""), + ty = it.type_(), + path = relpath + ); + if parentlen == 0 { + // There is no sidebar-items.js beyond the crate root path + // FIXME maybe dynamic crate loading can be merged here + } else { + write!(buffer, "<script defer src=\"{path}sidebar-items.js\"></script>", path = relpath); + } + // Closes sidebar-elems div. + write!(buffer, "</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, +) -> 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) { + 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: &str) -> String { + s.replace("<", "%3C") + .replace(">", "%3E") + .replace(" ", "%20") + .replace("?", "%3F") + .replace("'", "%27") + .replace("&", "%26") + .replace(",", "%2C") + .replace(":", "%3A") + .replace(";", "%3B") + .replace("[", "%5B") + .replace("]", "%5D") + .replace("\"", "%22") +} + +fn sidebar_assoc_items(it: &clean::Item) -> String { + let mut out = String::new(); + let c = cache(); + if let Some(v) = c.impls.get(&it.def_id) { + let mut used_links = FxHashSet::default(); + + { + 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)) + .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(&format!( + "<a class=\"sidebar-title\" href=\"#implementations\">Methods</a>\ + <div class=\"sidebar-links\">{}</div>", + ret.join("") + )); + } + } + + if v.iter().any(|i| i.inner_impl().trait_.is_some()) { + if let Some(impl_) = v + .iter() + .filter(|i| i.inner_impl().trait_.is_some()) + .find(|i| i.inner_impl().trait_.def_id() == c.deref_trait_did) + { + if let Some((target, real_target)) = + impl_.inner_impl().items.iter().find_map(|item| match item.inner { + clean::TypedefItem(ref t, true) => Some(match *t { + clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), + _ => (&t.type_, &t.type_), + }), + _ => None, + }) + { + let deref_mut = v + .iter() + .filter(|i| i.inner_impl().trait_.is_some()) + .any(|i| i.inner_impl().trait_.def_id() == c.deref_mut_trait_did); + let inner_impl = target + .def_id() + .or(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 { + out.push_str("<a class=\"sidebar-title\" href=\"#deref-methods\">"); + out.push_str(&format!( + "Methods from {}<Target={}>", + Escape(&format!( + "{:#}", + impl_.inner_impl().trait_.as_ref().unwrap().print() + )), + Escape(&format!("{:#}", real_target.print())) + )); + out.push_str("</a>"); + 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) + }) + .collect::<Vec<_>>(); + // We want links' order to be reproducible so we don't use unstable sort. + ret.sort(); + if !ret.is_empty() { + out.push_str(&format!( + "<div class=\"sidebar-links\">{}</div>", + ret.join("") + )); + } + } + } + } + let format_impls = |impls: Vec<&Impl>| { + let mut links = FxHashSet::default(); + + let mut ret = impls + .iter() + .filter_map(|i| { + let is_negative_impl = is_negative_impl(i.inner_impl()); + if let Some(ref i) = i.inner_impl().trait_ { + let i_display = format!("{:#}", i.print()); + let out = Escape(&i_display); + let encoded = small_url_encode(&format!("{:#}", i.print())); + let generated = format!( + "<a href=\"#impl-{}\">{}{}</a>", + encoded, + if is_negative_impl { "!" } else { "" }, + out + ); + if links.insert(generated.clone()) { Some(generated) } else { None } + } else { + None + } + }) + .collect::<Vec<String>>(); + ret.sort(); + ret.join("") + }; + + 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>", + ); + out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", concrete_format)); + } + + if !synthetic_format.is_empty() { + out.push_str( + "<a class=\"sidebar-title\" href=\"#synthetic-implementations\">\ + Auto Trait Implementations</a>", + ); + out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", synthetic_format)); + } + + if !blanket_format.is_empty() { + out.push_str( + "<a class=\"sidebar-title\" href=\"#blanket-implementations\">\ + Blanket Implementations</a>", + ); + out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", blanket_format)); + } + } + } + + out +} + +fn sidebar_struct(buf: &mut Buffer, it: &clean::Item, s: &clean::Struct) { + let mut sidebar = String::new(); + let fields = get_struct_fields_name(&s.fields); + + if !fields.is_empty() { + if let doctree::Plain = s.struct_type { + sidebar.push_str(&format!( + "<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\ + <div class=\"sidebar-links\">{}</div>", + fields + )); + } + } + + sidebar.push_str(&sidebar_assoc_items(it)); + + if !sidebar.is_empty() { + write!(buf, "<div class=\"block items\">{}</div>", sidebar); + } +} + +fn get_id_for_impl_on_foreign_type(for_: &clean::Type, trait_: &clean::Type) -> String { + small_url_encode(&format!("impl-{:#}-for-{:#}", trait_.print(), for_.print())) +} + +fn extract_for_impl_name(item: &clean::Item) -> Option<(String, String)> { + match item.inner { + clean::ItemEnum::ImplItem(ref i) => { + if let Some(ref trait_) = i.trait_ { + Some(( + format!("{:#}", i.for_.print()), + get_id_for_impl_on_foreign_type(&i.for_, trait_), + )) + } else { + None + } + } + _ => None, + } +} + +fn is_negative_impl(i: &clean::Impl) -> bool { + i.polarity == Some(clean::ImplPolarity::Negative) +} + +fn sidebar_trait(buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) { + let mut sidebar = String::new(); + + let mut types = t + .items + .iter() + .filter_map(|m| match m.name { + Some(ref name) if m.is_associated_type() => { + Some(format!("<a href=\"#associatedtype.{name}\">{name}</a>", name = name)) + } + _ => None, + }) + .collect::<Vec<_>>(); + let mut consts = t + .items + .iter() + .filter_map(|m| match m.name { + Some(ref name) if m.is_associated_const() => { + Some(format!("<a href=\"#associatedconstant.{name}\">{name}</a>", name = name)) + } + _ => None, + }) + .collect::<Vec<_>>(); + let mut required = t + .items + .iter() + .filter_map(|m| match m.name { + Some(ref name) if m.is_ty_method() => { + Some(format!("<a href=\"#tymethod.{name}\">{name}</a>", name = name)) + } + _ => None, + }) + .collect::<Vec<String>>(); + let mut provided = t + .items + .iter() + .filter_map(|m| match m.name { + Some(ref name) if m.is_method() => { + Some(format!("<a href=\"#method.{0}\">{0}</a>", name)) + } + _ => None, + }) + .collect::<Vec<String>>(); + + if !types.is_empty() { + types.sort(); + sidebar.push_str(&format!( + "<a class=\"sidebar-title\" href=\"#associated-types\">\ + Associated Types</a><div class=\"sidebar-links\">{}</div>", + types.join("") + )); + } + if !consts.is_empty() { + consts.sort(); + sidebar.push_str(&format!( + "<a class=\"sidebar-title\" href=\"#associated-const\">\ + Associated Constants</a><div class=\"sidebar-links\">{}</div>", + consts.join("") + )); + } + if !required.is_empty() { + required.sort(); + sidebar.push_str(&format!( + "<a class=\"sidebar-title\" href=\"#required-methods\">\ + Required Methods</a><div class=\"sidebar-links\">{}</div>", + required.join("") + )); + } + if !provided.is_empty() { + provided.sort(); + sidebar.push_str(&format!( + "<a class=\"sidebar-title\" href=\"#provided-methods\">\ + Provided Methods</a><div class=\"sidebar-links\">{}</div>", + provided.join("") + )); + } + + let c = cache(); + + if let Some(implementors) = c.implementors.get(&it.def_id) { + let mut res = implementors + .iter() + .filter(|i| i.inner_impl().for_.def_id().map_or(false, |d| !c.paths.contains_key(&d))) + .filter_map(|i| extract_for_impl_name(&i.impl_item)) + .collect::<Vec<_>>(); + + if !res.is_empty() { + res.sort(); + sidebar.push_str(&format!( + "<a class=\"sidebar-title\" href=\"#foreign-impls\">\ + Implementations on Foreign Types</a><div \ + class=\"sidebar-links\">{}</div>", + res.into_iter() + .map(|(name, id)| format!("<a href=\"#{}\">{}</a>", id, Escape(&name))) + .collect::<Vec<_>>() + .join("") + )); + } + } + + sidebar.push_str(&sidebar_assoc_items(it)); + + sidebar.push_str("<a class=\"sidebar-title\" href=\"#implementors\">Implementors</a>"); + if t.auto { + sidebar.push_str( + "<a class=\"sidebar-title\" \ + href=\"#synthetic-implementors\">Auto Implementors</a>", + ); + } + + write!(buf, "<div class=\"block items\">{}</div>", sidebar) +} + +fn sidebar_primitive(buf: &mut Buffer, it: &clean::Item) { + let sidebar = sidebar_assoc_items(it); + + if !sidebar.is_empty() { + write!(buf, "<div class=\"block items\">{}</div>", sidebar); + } +} + +fn sidebar_typedef(buf: &mut Buffer, it: &clean::Item) { + let sidebar = sidebar_assoc_items(it); + + if !sidebar.is_empty() { + write!(buf, "<div class=\"block items\">{}</div>", sidebar); + } +} + +fn get_struct_fields_name(fields: &[clean::Item]) -> String { + let mut fields = fields + .iter() + .filter(|f| if let clean::StructFieldItem(..) = f.inner { true } else { false }) + .filter_map(|f| match f.name { + Some(ref name) => { + Some(format!("<a href=\"#structfield.{name}\">{name}</a>", name = name)) + } + _ => None, + }) + .collect::<Vec<_>>(); + fields.sort(); + fields.join("") +} + +fn sidebar_union(buf: &mut Buffer, it: &clean::Item, u: &clean::Union) { + let mut sidebar = String::new(); + let fields = get_struct_fields_name(&u.fields); + + if !fields.is_empty() { + sidebar.push_str(&format!( + "<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\ + <div class=\"sidebar-links\">{}</div>", + fields + )); + } + + sidebar.push_str(&sidebar_assoc_items(it)); + + if !sidebar.is_empty() { + write!(buf, "<div class=\"block items\">{}</div>", sidebar); + } +} + +fn sidebar_enum(buf: &mut Buffer, it: &clean::Item, e: &clean::Enum) { + let mut sidebar = String::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.push_str(&sidebar_assoc_items(it)); + + if !sidebar.is_empty() { + write!(buf, "<div class=\"block items\">{}</div>", sidebar); + } +} + +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(); + + if items.iter().any(|it| it.type_() == ItemType::ExternCrate || it.type_() == ItemType::Import) + { + sidebar.push_str(&format!( + "<li><a href=\"#{id}\">{name}</a></li>", + id = "reexports", + name = "Re-exports" + )); + } + + // 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) { + let (short, name) = item_ty_to_strs(&myty); + sidebar.push_str(&format!( + "<li><a href=\"#{id}\">{name}</a></li>", + id = short, + name = name + )); + } + } + + if !sidebar.is_empty() { + write!(buf, "<div class=\"block items\"><ul>{}</ul></div>", sidebar); + } +} + +fn sidebar_foreign_type(buf: &mut Buffer, it: &clean::Item) { + let sidebar = sidebar_assoc_items(it); + if !sidebar.is_empty() { + write!(buf, "<div class=\"block items\">{}</div>", sidebar); + } +} + +fn item_macro(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Macro) { + wrap_into_docblock(w, |w| { + w.write_str(&highlight::render_with_highlighting( + t.source.clone(), + Some("macro"), + None, + None, + )) + }); + document(w, cx, it) +} + +fn item_proc_macro(w: &mut Buffer, cx: &Context, it: &clean::Item, m: &clean::ProcMacro) { + let name = it.name.as_ref().expect("proc-macros always have names"); + match m.kind { + MacroKind::Bang => { + write!(w, "<pre class='rust macro'>"); + write!(w, "{}!() {{ /* proc-macro */ }}", name); + write!(w, "</pre>"); + } + MacroKind::Attr => { + write!(w, "<pre class='rust attr'>"); + write!(w, "#[{}]", name); + write!(w, "</pre>"); + } + MacroKind::Derive => { + write!(w, "<pre class='rust derive'>"); + write!(w, "#[derive({})]", name); + if !m.helpers.is_empty() { + writeln!(w, "\n{{"); + writeln!(w, " // Attributes available to this derive:"); + for attr in &m.helpers { + writeln!(w, " #[{}]", attr); + } + write!(w, "}}"); + } + write!(w, "</pre>"); + } + } + document(w, cx, it) +} + +fn item_primitive(w: &mut Buffer, cx: &Context, it: &clean::Item, cache: &Cache) { + document(w, cx, it); + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All, cache) +} + +fn item_keyword(w: &mut Buffer, cx: &Context, it: &clean::Item) { + document(w, cx, it) +} + +crate const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang"; + +fn make_item_keywords(it: &clean::Item) -> String { + format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap()) +} + +/// 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) -> Vec<String> { + let mut out = Vec::new(); + let mut visited = FxHashSet::default(); + let mut work = VecDeque::new(); + let cache = cache(); + + 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 +} |
