From c692ed468c8dc4b9f549ef839b4b490e3b84d19c Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Mon, 15 Jun 2020 13:42:29 -0500 Subject: Move `Error` and `RenderInfo` out of `html` module --- src/librustdoc/config.rs | 17 +++++++++ src/librustdoc/core.rs | 2 +- src/librustdoc/error.rs | 56 +++++++++++++++++++++++++++++ src/librustdoc/html/render.rs | 71 +++---------------------------------- src/librustdoc/html/render/cache.rs | 3 +- src/librustdoc/html/sources.rs | 3 +- src/librustdoc/lib.rs | 4 ++- 7 files changed, 85 insertions(+), 71 deletions(-) create mode 100644 src/librustdoc/error.rs (limited to 'src') diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 39e33da4496..1ea9e28ae42 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -4,6 +4,9 @@ use std::ffi::OsStr; use std::fmt; use std::path::PathBuf; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def_id::DefId; +use rustc_middle::middle::privacy::AccessLevels; use rustc_session::config::{self, parse_crate_types_from_list, parse_externs, CrateType}; use rustc_session::config::{ build_codegen_options, build_debugging_options, get_cmd_lint_options, host_triple, @@ -249,6 +252,20 @@ pub struct RenderOptions { pub document_hidden: bool, } +/// Temporary storage for data obtained during `RustdocVisitor::clean()`. +/// Later on moved into `CACHE_KEY`. +#[derive(Default)] +pub struct RenderInfo { + pub inlined: FxHashSet, + pub external_paths: crate::core::ExternalPaths, + pub exact_paths: FxHashMap>, + pub access_levels: AccessLevels, + pub deref_trait_did: Option, + pub deref_mut_trait_did: Option, + pub owned_box_did: Option, + pub output_format: Option, +} + impl Options { /// Parses the given command-line for options. If an error message or other early-return has /// been printed, returns `Err` with the exit code. diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 263909d5559..85bb7ca4cd6 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -32,8 +32,8 @@ use std::rc::Rc; use crate::clean; use crate::clean::{AttributesExt, MAX_DEF_ID}; +use crate::config::RenderInfo; use crate::config::{Options as RustdocOptions, RenderOptions}; -use crate::html::render::RenderInfo; use crate::passes::{self, Condition::*, ConditionalPass}; pub use rustc_session::config::{CodegenOptions, DebuggingOptions, Input, Options}; diff --git a/src/librustdoc/error.rs b/src/librustdoc/error.rs new file mode 100644 index 00000000000..77063ab4639 --- /dev/null +++ b/src/librustdoc/error.rs @@ -0,0 +1,56 @@ +use std::error; +use std::fmt::{self, Formatter}; +use std::path::{Path, PathBuf}; + +use crate::docfs::PathError; + +#[derive(Debug)] +pub struct Error { + pub file: PathBuf, + pub error: String, +} + +impl error::Error for Error {} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let file = self.file.display().to_string(); + if file.is_empty() { + write!(f, "{}", self.error) + } else { + write!(f, "\"{}\": {}", self.file.display(), self.error) + } + } +} + +impl PathError for Error { + fn new>(e: S, path: P) -> Error + where + S: ToString + Sized, + { + Error { file: path.as_ref().to_path_buf(), error: e.to_string() } + } +} + +#[macro_export] +macro_rules! try_none { + ($e:expr, $file:expr) => {{ + use std::io; + match $e { + Some(e) => e, + None => { + return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"), $file)); + } + } + }}; +} + +#[macro_export] +macro_rules! try_err { + ($e:expr, $file:expr) => {{ + match $e { + Ok(e) => e, + Err(e) => return Err(Error::new(e, $file)), + } + }}; +} diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index f7050cf3777..18ef97118e0 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -30,9 +30,8 @@ use std::cell::{Cell, RefCell}; use std::cmp::Ordering; use std::collections::{BTreeMap, VecDeque}; use std::default::Default; -use std::error; use std::ffi::OsStr; -use std::fmt::{self, Formatter, Write}; +use std::fmt::{self, Write}; use std::fs::{self, File}; use std::io::prelude::*; use std::io::{self, BufReader}; @@ -50,7 +49,6 @@ 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::privacy::AccessLevels; use rustc_middle::middle::stability; use rustc_span::edition::Edition; use rustc_span::hygiene::MacroKind; @@ -60,9 +58,11 @@ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, TypeKind}; -use crate::config::{OutputFormat, RenderOptions}; +use crate::config::RenderInfo; +use crate::config::RenderOptions; use crate::docfs::{DocFS, ErrorStorage, PathError}; use crate::doctree; +use crate::error::Error; use crate::html::escape::Escape; use crate::html::format::fmt_impl_for_trait_page; use crate::html::format::Function; @@ -90,55 +90,6 @@ crate fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { }) } -#[derive(Debug)] -pub struct Error { - pub file: PathBuf, - pub error: String, -} - -impl error::Error for Error {} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let file = self.file.display().to_string(); - if file.is_empty() { - write!(f, "{}", self.error) - } else { - write!(f, "\"{}\": {}", self.file.display(), self.error) - } - } -} - -impl PathError for Error { - fn new>(e: S, path: P) -> Error - where - S: ToString + Sized, - { - Error { file: path.as_ref().to_path_buf(), error: e.to_string() } - } -} - -macro_rules! try_none { - ($e:expr, $file:expr) => {{ - use std::io; - match $e { - Some(e) => e, - None => { - return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"), $file)); - } - } - }}; -} - -macro_rules! try_err { - ($e:expr, $file:expr) => {{ - match $e { - Ok(e) => e, - Err(e) => return Err(Error::new(e, $file)), - } - }}; -} - /// 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. @@ -260,20 +211,6 @@ impl Impl { } } -/// Temporary storage for data obtained during `RustdocVisitor::clean()`. -/// Later on moved into `CACHE_KEY`. -#[derive(Default)] -pub struct RenderInfo { - pub inlined: FxHashSet, - pub external_paths: crate::core::ExternalPaths, - pub exact_paths: FxHashMap>, - pub access_levels: AccessLevels, - pub deref_trait_did: Option, - pub deref_mut_trait_did: Option, - pub owned_box_did: Option, - pub output_format: Option, -} - // Helper structs for rendering items/sidebars and carrying along contextual // information diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs index 1b5c8a9378e..6da4a4628e8 100644 --- a/src/librustdoc/html/render/cache.rs +++ b/src/librustdoc/html/render/cache.rs @@ -12,7 +12,8 @@ use std::path::{Path, PathBuf}; use serde::Serialize; use super::{plain_summary_line, shorten, Impl, IndexItem, IndexItemFunctionType, ItemType}; -use super::{Generic, RenderInfo, RenderType, TypeWithKind}; +use super::{Generic, RenderType, TypeWithKind}; +use crate::config::RenderInfo; /// Indicates where an external crate can be found. pub enum ExternalLocation { diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index e3215921f12..aaa73b100c2 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -1,10 +1,11 @@ use crate::clean; use crate::docfs::PathError; +use crate::error::Error; use crate::fold::DocFolder; use crate::html::format::Buffer; use crate::html::highlight; use crate::html::layout; -use crate::html::render::{Error, SharedContext, BASIC_KEYWORDS}; +use crate::html::render::{SharedContext, BASIC_KEYWORDS}; use rustc_hir::def_id::LOCAL_CRATE; use rustc_span::source_map::FileName; use std::ffi::OsStr; diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index cbf53d52ef0..ac31ab5980c 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -63,6 +63,8 @@ mod config; mod core; mod docfs; mod doctree; +#[macro_use] +mod error; mod fold; pub mod html { crate mod escape; @@ -85,7 +87,7 @@ mod visit_lib; struct Output { krate: clean::Crate, - renderinfo: html::render::RenderInfo, + renderinfo: config::RenderInfo, renderopts: config::RenderOptions, } -- cgit 1.4.1-3-g733a5 From 5bc97946ca35a789b690668bb6b27ca41bfeb5b2 Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Wed, 17 Jun 2020 12:41:45 -0500 Subject: Refactor html backend to use generic interface --- src/librustdoc/formats/mod.rs | 107 ++++++++ src/librustdoc/html/render.rs | 610 +++++++++++++++++++++--------------------- src/librustdoc/lib.rs | 5 +- 3 files changed, 409 insertions(+), 313 deletions(-) create mode 100644 src/librustdoc/formats/mod.rs (limited to 'src') diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs new file mode 100644 index 00000000000..55827f47385 --- /dev/null +++ b/src/librustdoc/formats/mod.rs @@ -0,0 +1,107 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use rustc_span::edition::Edition; + +use crate::clean; +use crate::config::{RenderInfo, RenderOptions}; +use crate::error::Error; + +pub trait FormatRenderer: Clone { + type Output: FormatRenderer; + + fn init( + krate: clean::Crate, + options: RenderOptions, + renderinfo: RenderInfo, + diag: &rustc_errors::Handler, + edition: Edition, + parent: Rc>, + ) -> Result<(Self::Output, clean::Crate), Error>; + + /// Renders a single non-module item. This means no recursive sub-item rendering is required. + fn item(&mut self, item: clean::Item) -> Result<(), Error>; + + /// Renders a module. Doesn't need to handle recursing into children, the driver does that + /// automatically. + fn mod_item_in( + &mut self, + item: &clean::Item, + item_name: &str, + module: &clean::Module, + ) -> Result<(), Error>; + + /// Runs after recursively rendering all sub-items of a module. + fn mod_item_out(&mut self) -> Result<(), Error>; + + /// Post processing hook for cleanup and dumping output to files. + fn after_krate(&mut self, krate: &clean::Crate) -> Result<(), Error>; + + /// Called after everything else to write out errors. + fn after_run(&mut self, diag: &rustc_errors::Handler) -> Result<(), Error>; +} + +#[derive(Clone)] +pub struct Renderer; + +impl Renderer { + pub fn new() -> Renderer { + Renderer + } + + /// Main method for rendering a crate. + pub fn run( + self, + krate: clean::Crate, + options: RenderOptions, + renderinfo: RenderInfo, + diag: &rustc_errors::Handler, + edition: Edition, + ) -> Result<(), Error> { + let rself = Rc::new(RefCell::new(self)); + let (mut renderer, mut krate) = + T::init(krate, options, renderinfo, diag, edition, rself.clone())?; + let mut item = match krate.module.take() { + Some(i) => i, + None => return Ok(()), + }; + + item.name = Some(krate.name.clone()); + + // Render the crate documentation + let mut work = vec![(renderer.clone(), item)]; + + while let Some((mut cx, item)) = work.pop() { + if item.is_mod() { + // modules are special because they add a namespace. We also need to + // recurse into the items of the module as well. + let name = item.name.as_ref().unwrap().to_string(); + if name.is_empty() { + panic!("Unexpected module with empty name"); + } + + let module = match item.inner { + clean::StrippedItem(box clean::ModuleItem(ref m)) + | clean::ModuleItem(ref m) => m, + _ => unreachable!(), + }; + cx.mod_item_in(&item, &name, module)?; + let module = match item.inner { + clean::StrippedItem(box clean::ModuleItem(m)) | clean::ModuleItem(m) => m, + _ => unreachable!(), + }; + for it in module.items { + info!("Adding {:?} to worklist", it.name); + work.push((cx.clone(), it)); + } + + cx.mod_item_out()?; + } else if item.name.is_some() { + cx.item(item)?; + } + } + + renderer.after_krate(&krate)?; + renderer.after_run(diag) + } +} diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 18ef97118e0..fd32ba66d91 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -63,6 +63,7 @@ use crate::config::RenderOptions; use crate::docfs::{DocFS, ErrorStorage, PathError}; use crate::doctree; use crate::error::Error; +use crate::formats::{FormatRenderer, Renderer}; use crate::html::escape::Escape; use crate::html::format::fmt_impl_for_trait_page; use crate::html::format::Function; @@ -98,7 +99,7 @@ crate fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { /// easily cloned because it is cloned per work-job (about once per item in the /// rustdoc tree). #[derive(Clone)] -struct Context { +crate struct Context { /// Current hierarchy of components leading down to what's currently being /// rendered pub current: Vec, @@ -113,6 +114,9 @@ struct Context { id_map: Rc>, pub shared: Arc, pub cache: Arc, + pub parent: Rc>, + all: Rc>, + pub errors: Arc, } crate struct SharedContext { @@ -390,148 +394,307 @@ pub fn initial_ids() -> Vec { .collect() } -/// Generates the documentation for `crate` into the directory `dst` -pub fn run( - mut krate: clean::Crate, - options: RenderOptions, - renderinfo: RenderInfo, - diag: &rustc_errors::Handler, - edition: Edition, -) -> Result<(), 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, - extern_html_root_urls, - resource_suffix, - static_root_path, - generate_search_filter, - document_private, - .. - } = 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(), - }; - let mut errors = Arc::new(ErrorStorage::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; +impl FormatRenderer for Context { + type Output = Self; + + /// Generates the documentation for `crate` into the directory `dst` + fn init( + mut krate: clean::Crate, + options: RenderOptions, + renderinfo: RenderInfo, + _diag: &rustc_errors::Handler, + edition: Edition, + parent: Rc>, + ) -> 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, + extern_html_root_urls, + resource_suffix, + static_root_path, + generate_search_filter, + document_private, + .. + } = 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(), + }; + let errors = Arc::new(ErrorStorage::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 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(&errors), + 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)?; + let (new_crate, index, cache) = + Cache::from_krate(renderinfo, document_private, &extern_html_root_urls, &dst, krate); + krate = new_crate; + 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), + cache: cache.clone(), + parent, + all: Rc::new(RefCell::new(AllTypes::new())), + errors, + }; + + // Freeze the cache now that the index has been built. Put an Arc into TLS + // for future parallelization opportunities + CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); + 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)?; + Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + Ok((cx, krate)) } - 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(&errors), - 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)?; - let (new_crate, index, cache) = - Cache::from_krate(renderinfo, document_private, &extern_html_root_urls, &dst, krate); - krate = new_crate; - 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), - cache: cache.clone(), - }; + fn after_run(&mut self, diag: &rustc_errors::Handler) -> Result<(), Error> { + let nb_errors = + Arc::get_mut(&mut self.errors).map_or_else(|| 0, |errors| errors.write_errors(diag)); + 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) -> 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) = self.cache.crate_version { + format!( + "

Crate {}

\ +
\ +

Version {}

\ +
\ +

Back to index

", + 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 = "

Settings

"; + 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, + module: &clean::Module, + ) -> 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); + // 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())?; + } - // Freeze the cache now that the index has been built. Put an Arc into TLS - // for future parallelization opportunities - CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); - CURRENT_DEPTH.with(|s| s.set(0)); + // Render sidebar-items.js used throughout this module. + if !self.render_redirect_pages { + 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(()) + } - // 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)?; - Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + fn mod_item_out(&mut self) -> Result<(), Error> { + info!("Recursed; leaving {}", self.dst.display()); - // And finally render the whole crate's documentation - let ret = cx.krate(krate); - let nb_errors = Arc::get_mut(&mut errors).map_or_else(|| 0, |errors| errors.write_errors(diag)); - if ret.is_err() { - ret - } else if nb_errors > 0 { - Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) - } else { + // Go back to where we were at + self.dst.pop(); + self.current.pop(); + Ok(()) + } + + fn item(&mut self, item: clean::Item) -> 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); + // 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(()) } } @@ -1291,92 +1454,6 @@ impl Context { "../".repeat(self.current.len()) } - /// Main method for rendering a crate. - /// - /// This currently isn't parallelized, but it'd be pretty easy to add - /// parallelization to this function. - fn krate(self, mut krate: clean::Crate) -> Result<(), Error> { - let mut item = match krate.module.take() { - Some(i) => i, - None => return Ok(()), - }; - 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(); - item.name = Some(krate.name); - - let mut all = AllTypes::new(); - - { - // Render the crate documentation - let mut work = vec![(self.clone(), item)]; - - while let Some((mut cx, item)) = work.pop() { - cx.item(item, &mut all, |cx, item| work.push((cx.clone(), item)))? - } - } - - 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) = self.cache.crate_version { - format!( - "

Crate {}

\ -
\ -

Version {}

\ -
\ -

Back to index

", - crate_name, - Escape(version), - ) - } else { - String::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 = "

Settings

"; - 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 render_item(&self, it: &clean::Item, pushname: bool) -> String { // A little unfortunate that this is done like this, but it sure // does make formatting *a lot* nicer. @@ -1449,97 +1526,6 @@ impl Context { } } - /// Non-parallelized version of rendering an item. This will take the input - /// item, render its contents, and then invoke the specified closure with - /// all sub-items which need to be rendered. - /// - /// The rendering driver uses this closure to queue up more work. - fn item(&mut self, item: clean::Item, all: &mut AllTypes, mut f: F) -> Result<(), Error> - where - F: FnMut(&mut Context, clean::Item), - { - // 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(); - } - - if item.is_mod() { - // modules are special because they add a namespace. We also need to - // recurse into the items of the module as well. - let name = item.name.as_ref().unwrap().to_string(); - let scx = &self.shared; - if name.is_empty() { - panic!("Unexpected empty destination: {:?}", self.current); - } - let prev = self.dst.clone(); - self.dst.push(&name); - self.current.push(name); - - info!("Recursing into {}", self.dst.display()); - - let buf = self.render_item(&item, false); - // 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())?; - } - - let m = match item.inner { - clean::StrippedItem(box clean::ModuleItem(m)) | clean::ModuleItem(m) => m, - _ => unreachable!(), - }; - - // Render sidebar-items.js used throughout this module. - if !self.render_redirect_pages { - let items = self.build_sidebar_items(&m); - 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)?; - } - - for item in m.items { - f(self, item); - } - - info!("Recursed; leaving {}", self.dst.display()); - - // Go back to where we were at - self.dst = prev; - self.current.pop().unwrap(); - } else if item.name.is_some() { - let buf = self.render_item(&item, true); - // 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 { - all.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 build_sidebar_items(&self, m: &clean::Module) -> BTreeMap> { // BTreeMap instead of HashMap to get a sorted output let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index ac31ab5980c..715956ea172 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -66,6 +66,7 @@ mod doctree; #[macro_use] mod error; mod fold; +mod formats; pub mod html { crate mod escape; crate mod format; @@ -512,7 +513,9 @@ fn main_options(options: config::Options) -> i32 { info!("going to format"); let (error_format, edition, debugging_options) = diag_opts; let diag = core::new_handler(error_format, None, &debugging_options); - match html::render::run(krate, renderopts, renderinfo, &diag, edition) { + match formats::Renderer::new() + .run::(krate, renderopts, renderinfo, &diag, edition) + { Ok(_) => rustc_driver::EXIT_SUCCESS, Err(e) => { diag.struct_err(&format!("couldn't generate documentation: {}", e.error)) -- cgit 1.4.1-3-g733a5 From 6a4396b98c6fcb405429a9798a9ab6554f015b7e Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Wed, 24 Jun 2020 08:16:21 -0500 Subject: Extract `Cache` and other types from `html` module --- src/librustdoc/clean/types.rs | 7 +- src/librustdoc/config.rs | 2 +- src/librustdoc/core.rs | 4 +- src/librustdoc/formats/cache.rs | 498 ++++ src/librustdoc/formats/item_type.rs | 163 ++ src/librustdoc/formats/mod.rs | 116 +- src/librustdoc/formats/renderer.rs | 112 + src/librustdoc/html/format.rs | 20 +- src/librustdoc/html/item_type.rs | 163 -- src/librustdoc/html/mod.rs | 9 + src/librustdoc/html/render.rs | 4599 ----------------------------------- src/librustdoc/html/render/cache.rs | 501 +--- src/librustdoc/html/render/mod.rs | 4599 +++++++++++++++++++++++++++++++++++ src/librustdoc/lib.rs | 15 +- 14 files changed, 5432 insertions(+), 5376 deletions(-) create mode 100644 src/librustdoc/formats/cache.rs create mode 100644 src/librustdoc/formats/item_type.rs create mode 100644 src/librustdoc/formats/renderer.rs delete mode 100644 src/librustdoc/html/item_type.rs create mode 100644 src/librustdoc/html/mod.rs delete mode 100644 src/librustdoc/html/render.rs create mode 100644 src/librustdoc/html/render/mod.rs (limited to 'src') diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 071834c59d6..89549eae2cb 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -32,8 +32,9 @@ use crate::clean::inline; use crate::clean::types::Type::{QPath, ResolvedPath}; use crate::core::DocContext; use crate::doctree; -use crate::html::item_type::ItemType; -use crate::html::render::{cache, ExternalLocation}; +use crate::formats::cache::cache; +use crate::formats::item_type::ItemType; +use crate::html::render::cache::ExternalLocation; use self::FnRetTy::*; use self::ItemEnum::*; @@ -1172,7 +1173,7 @@ impl GetDefId for Type { fn def_id(&self) -> Option { match *self { ResolvedPath { did, .. } => Some(did), - Primitive(p) => crate::html::render::cache().primitive_locations.get(&p).cloned(), + Primitive(p) => cache().primitive_locations.get(&p).cloned(), BorrowedRef { type_: box Generic(..), .. } => { Primitive(PrimitiveType::Reference).def_id() } diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 1ea9e28ae42..3547b45dfa7 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -254,7 +254,7 @@ pub struct RenderOptions { /// Temporary storage for data obtained during `RustdocVisitor::clean()`. /// Later on moved into `CACHE_KEY`. -#[derive(Default)] +#[derive(Default, Clone)] pub struct RenderInfo { pub inlined: FxHashSet, pub external_paths: crate::core::ExternalPaths, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 85bb7ca4cd6..7635453d56f 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -44,9 +44,9 @@ pub type ExternalPaths = FxHashMap, clean::TypeKind)>; pub struct DocContext<'tcx> { pub tcx: TyCtxt<'tcx>, pub resolver: Rc>, - /// Later on moved into `html::render::CACHE_KEY` + /// Later on moved into `formats::cache::CACHE_KEY` pub renderinfo: RefCell, - /// Later on moved through `clean::Crate` into `html::render::CACHE_KEY` + /// Later on moved through `clean::Crate` into `formats::cache::CACHE_KEY` pub external_traits: Rc>>, /// Used while populating `external_traits` to ensure we don't process the same trait twice at /// the same time. diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs new file mode 100644 index 00000000000..4bdeafa1ec7 --- /dev/null +++ b/src/librustdoc/formats/cache.rs @@ -0,0 +1,498 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::mem; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +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 crate::clean::{self, GetDefId}; +use crate::config::RenderInfo; +use crate::fold::DocFolder; +use crate::formats::item_type::ItemType; +use crate::formats::Impl; +use crate::html::render::cache::{extern_location, get_index_search_type, ExternalLocation}; +use crate::html::render::IndexItem; +use crate::html::render::{plain_summary_line, shorten}; + +thread_local!(crate static CACHE_KEY: RefCell> = Default::default()); + +/// 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)] +pub 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>, + + /// 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, ItemType)>, + + /// Similar to `paths`, but only holds external paths. This is only used for + /// generating explicit hyperlinks to other crates. + pub external_paths: FxHashMap, 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>, + + /// 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, + + /// 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>, + + /// Cache of where external crate documentation can be found. + pub extern_locations: FxHashMap, + + /// Cache of where documentation for primitives can be found. + pub primitive_locations: FxHashMap, + + // 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, + + /// The version of the crate being documented, if given from the `--crate-version` flag. + pub crate_version: Option, + + /// 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, + parent_stack: Vec, + parent_is_trait_impl: bool, + stripped_mod: bool, + masked_crates: FxHashSet, + + pub search_index: Vec, + pub deref_trait_did: Option, + pub deref_mut_trait_did: Option, + pub owned_box_did: Option, + + // 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. + pub 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, 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 aliases: BTreeMap>, +} + +impl Cache { + pub fn from_krate( + renderinfo: RenderInfo, + document_private: bool, + extern_html_root_urls: &BTreeMap, + dst: &Path, + mut krate: clean::Crate, + ) -> (clean::Crate, 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 + // TODO: this part is specific to HTML so it'd be nice to remove it from the common code + 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()); + } + } + } + + (krate, cache) + } +} + +impl DocFolder for Cache { + fn fold_item(&mut self, item: clean::Item) -> Option { + 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 + } +} + +crate fn cache() -> Arc { + CACHE_KEY.with(|c| c.borrow().clone()) +} diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs new file mode 100644 index 00000000000..696bdae94fc --- /dev/null +++ b/src/librustdoc/formats/item_type.rs @@ -0,0 +1,163 @@ +//! Item types. + +use std::fmt; + +use serde::{Serialize, Serializer}; + +use rustc_span::hygiene::MacroKind; + +use crate::clean; + +/// Item type. Corresponds to `clean::ItemEnum` variants. +/// +/// The search index uses item types encoded as smaller numbers which equal to +/// discriminants. JavaScript then is used to decode them into the original value. +/// Consequently, every change to this type should be synchronized to +/// the `itemTypes` mapping table in `html/static/main.js`. +/// +/// In addition, code in `html::render` uses this enum to generate CSS classes, page prefixes, and +/// module headings. If you are adding to this enum and want to ensure that the sidebar also prints +/// a heading, edit the listing in `html/render.rs`, function `sidebar_module`. This uses an +/// ordering based on a helper function inside `item_module`, in the same file. +#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord)] +pub enum ItemType { + Module = 0, + ExternCrate = 1, + Import = 2, + Struct = 3, + Enum = 4, + Function = 5, + Typedef = 6, + Static = 7, + Trait = 8, + Impl = 9, + TyMethod = 10, + Method = 11, + StructField = 12, + Variant = 13, + Macro = 14, + Primitive = 15, + AssocType = 16, + Constant = 17, + AssocConst = 18, + Union = 19, + ForeignType = 20, + Keyword = 21, + OpaqueTy = 22, + ProcAttribute = 23, + ProcDerive = 24, + TraitAlias = 25, +} + +impl Serialize for ItemType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + (*self as u8).serialize(serializer) + } +} + +impl<'a> From<&'a clean::Item> for ItemType { + fn from(item: &'a clean::Item) -> ItemType { + let inner = match item.inner { + clean::StrippedItem(box ref item) => item, + ref inner => inner, + }; + + match *inner { + clean::ModuleItem(..) => ItemType::Module, + clean::ExternCrateItem(..) => ItemType::ExternCrate, + clean::ImportItem(..) => ItemType::Import, + clean::StructItem(..) => ItemType::Struct, + clean::UnionItem(..) => ItemType::Union, + clean::EnumItem(..) => ItemType::Enum, + clean::FunctionItem(..) => ItemType::Function, + clean::TypedefItem(..) => ItemType::Typedef, + clean::OpaqueTyItem(..) => ItemType::OpaqueTy, + clean::StaticItem(..) => ItemType::Static, + clean::ConstantItem(..) => ItemType::Constant, + clean::TraitItem(..) => ItemType::Trait, + clean::ImplItem(..) => ItemType::Impl, + clean::TyMethodItem(..) => ItemType::TyMethod, + clean::MethodItem(..) => ItemType::Method, + clean::StructFieldItem(..) => ItemType::StructField, + clean::VariantItem(..) => ItemType::Variant, + clean::ForeignFunctionItem(..) => ItemType::Function, // no ForeignFunction + clean::ForeignStaticItem(..) => ItemType::Static, // no ForeignStatic + clean::MacroItem(..) => ItemType::Macro, + clean::PrimitiveItem(..) => ItemType::Primitive, + clean::AssocConstItem(..) => ItemType::AssocConst, + clean::AssocTypeItem(..) => ItemType::AssocType, + clean::ForeignTypeItem => ItemType::ForeignType, + clean::KeywordItem(..) => ItemType::Keyword, + clean::TraitAliasItem(..) => ItemType::TraitAlias, + clean::ProcMacroItem(ref mac) => match mac.kind { + MacroKind::Bang => ItemType::Macro, + MacroKind::Attr => ItemType::ProcAttribute, + MacroKind::Derive => ItemType::ProcDerive, + }, + clean::StrippedItem(..) => unreachable!(), + } + } +} + +impl From for ItemType { + fn from(kind: clean::TypeKind) -> ItemType { + match kind { + clean::TypeKind::Struct => ItemType::Struct, + clean::TypeKind::Union => ItemType::Union, + clean::TypeKind::Enum => ItemType::Enum, + clean::TypeKind::Function => ItemType::Function, + clean::TypeKind::Trait => ItemType::Trait, + clean::TypeKind::Module => ItemType::Module, + clean::TypeKind::Static => ItemType::Static, + clean::TypeKind::Const => ItemType::Constant, + clean::TypeKind::Typedef => ItemType::Typedef, + clean::TypeKind::Foreign => ItemType::ForeignType, + clean::TypeKind::Macro => ItemType::Macro, + clean::TypeKind::Attr => ItemType::ProcAttribute, + clean::TypeKind::Derive => ItemType::ProcDerive, + clean::TypeKind::TraitAlias => ItemType::TraitAlias, + } + } +} + +impl ItemType { + pub fn as_str(&self) -> &'static str { + match *self { + ItemType::Module => "mod", + ItemType::ExternCrate => "externcrate", + ItemType::Import => "import", + ItemType::Struct => "struct", + ItemType::Union => "union", + ItemType::Enum => "enum", + ItemType::Function => "fn", + ItemType::Typedef => "type", + ItemType::Static => "static", + ItemType::Trait => "trait", + ItemType::Impl => "impl", + ItemType::TyMethod => "tymethod", + ItemType::Method => "method", + ItemType::StructField => "structfield", + ItemType::Variant => "variant", + ItemType::Macro => "macro", + ItemType::Primitive => "primitive", + ItemType::AssocType => "associatedtype", + ItemType::Constant => "constant", + ItemType::AssocConst => "associatedconstant", + ItemType::ForeignType => "foreigntype", + ItemType::Keyword => "keyword", + ItemType::OpaqueTy => "opaque", + ItemType::ProcAttribute => "attr", + ItemType::ProcDerive => "derive", + ItemType::TraitAlias => "traitalias", + } + } +} + +impl fmt::Display for ItemType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index 55827f47385..2473f7758d2 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -1,107 +1,29 @@ -use std::cell::RefCell; -use std::rc::Rc; +pub mod cache; +pub mod item_type; +pub mod renderer; -use rustc_span::edition::Edition; +pub use renderer::{FormatRenderer, Renderer}; -use crate::clean; -use crate::config::{RenderInfo, RenderOptions}; -use crate::error::Error; - -pub trait FormatRenderer: Clone { - type Output: FormatRenderer; - - fn init( - krate: clean::Crate, - options: RenderOptions, - renderinfo: RenderInfo, - diag: &rustc_errors::Handler, - edition: Edition, - parent: Rc>, - ) -> Result<(Self::Output, clean::Crate), Error>; - - /// Renders a single non-module item. This means no recursive sub-item rendering is required. - fn item(&mut self, item: clean::Item) -> Result<(), Error>; +use rustc_span::def_id::DefId; - /// Renders a module. Doesn't need to handle recursing into children, the driver does that - /// automatically. - fn mod_item_in( - &mut self, - item: &clean::Item, - item_name: &str, - module: &clean::Module, - ) -> Result<(), Error>; - - /// Runs after recursively rendering all sub-items of a module. - fn mod_item_out(&mut self) -> Result<(), Error>; - - /// Post processing hook for cleanup and dumping output to files. - fn after_krate(&mut self, krate: &clean::Crate) -> Result<(), Error>; +use crate::clean; +use crate::clean::types::GetDefId; - /// Called after everything else to write out errors. - fn after_run(&mut self, diag: &rustc_errors::Handler) -> Result<(), Error>; +/// Metadata about implementations for a type or trait. +#[derive(Clone, Debug)] +pub struct Impl { + pub impl_item: clean::Item, } -#[derive(Clone)] -pub struct Renderer; - -impl Renderer { - pub fn new() -> Renderer { - Renderer - } - - /// Main method for rendering a crate. - pub fn run( - self, - krate: clean::Crate, - options: RenderOptions, - renderinfo: RenderInfo, - diag: &rustc_errors::Handler, - edition: Edition, - ) -> Result<(), Error> { - let rself = Rc::new(RefCell::new(self)); - let (mut renderer, mut krate) = - T::init(krate, options, renderinfo, diag, edition, rself.clone())?; - let mut item = match krate.module.take() { - Some(i) => i, - None => return Ok(()), - }; - - item.name = Some(krate.name.clone()); - - // Render the crate documentation - let mut work = vec![(renderer.clone(), item)]; - - while let Some((mut cx, item)) = work.pop() { - if item.is_mod() { - // modules are special because they add a namespace. We also need to - // recurse into the items of the module as well. - let name = item.name.as_ref().unwrap().to_string(); - if name.is_empty() { - panic!("Unexpected module with empty name"); - } - - let module = match item.inner { - clean::StrippedItem(box clean::ModuleItem(ref m)) - | clean::ModuleItem(ref m) => m, - _ => unreachable!(), - }; - cx.mod_item_in(&item, &name, module)?; - let module = match item.inner { - clean::StrippedItem(box clean::ModuleItem(m)) | clean::ModuleItem(m) => m, - _ => unreachable!(), - }; - for it in module.items { - info!("Adding {:?} to worklist", it.name); - work.push((cx.clone(), it)); - } - - cx.mod_item_out()?; - } else if item.name.is_some() { - cx.item(item)?; - } +impl Impl { + pub fn inner_impl(&self) -> &clean::Impl { + match self.impl_item.inner { + clean::ImplItem(ref impl_) => impl_, + _ => panic!("non-impl item found in impl"), } + } - renderer.after_krate(&krate)?; - renderer.after_run(diag) + pub fn trait_did(&self) -> Option { + self.inner_impl().trait_.def_id() } } diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs new file mode 100644 index 00000000000..f1862337ba1 --- /dev/null +++ b/src/librustdoc/formats/renderer.rs @@ -0,0 +1,112 @@ +use std::sync::Arc; + +use rustc_span::edition::Edition; + +use crate::clean; +use crate::config::{RenderInfo, RenderOptions}; +use crate::error::Error; +use crate::formats::cache::{Cache, CACHE_KEY}; + +pub trait FormatRenderer: Clone { + type Output: FormatRenderer; + + fn init( + krate: clean::Crate, + options: RenderOptions, + renderinfo: RenderInfo, + edition: Edition, + cache: &mut Cache, + ) -> Result<(Self::Output, clean::Crate), Error>; + + /// Renders a single non-module item. This means no recursive sub-item rendering is required. + fn item(&mut self, item: clean::Item, cache: &Cache) -> Result<(), Error>; + + /// Renders a module (doesn't need to handle recursing into children). + fn mod_item_in( + &mut self, + item: &clean::Item, + item_name: &str, + cache: &Cache, + ) -> Result<(), Error>; + + /// Runs after recursively rendering all sub-items of a module. + fn mod_item_out(&mut self, name: &str) -> Result<(), Error>; + + /// Post processing hook for cleanup and dumping output to files. + fn after_krate(&mut self, krate: &clean::Crate, cache: &Cache) -> Result<(), Error>; + + /// Called after everything else to write out errors. + fn after_run(&mut self, diag: &rustc_errors::Handler) -> Result<(), Error>; +} + +#[derive(Clone)] +pub struct Renderer; + +impl Renderer { + pub fn new() -> Renderer { + Renderer + } + + /// Main method for rendering a crate. + pub fn run( + self, + krate: clean::Crate, + options: RenderOptions, + renderinfo: RenderInfo, + diag: &rustc_errors::Handler, + edition: Edition, + ) -> Result<(), Error> { + let (krate, mut cache) = Cache::from_krate( + renderinfo.clone(), + options.document_private, + &options.extern_html_root_urls, + &options.output, + krate, + ); + + let (mut renderer, mut krate) = T::init(krate, options, renderinfo, edition, &mut cache)?; + + let cache = Arc::new(cache); + // Freeze the cache now that the index has been built. Put an Arc into TLS for future + // parallelization opportunities + CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); + + let mut item = match krate.module.take() { + Some(i) => i, + None => return Ok(()), + }; + + item.name = Some(krate.name.clone()); + + // Render the crate documentation + let mut work = vec![(renderer.clone(), item)]; + + while let Some((mut cx, item)) = work.pop() { + if item.is_mod() { + // modules are special because they add a namespace. We also need to + // recurse into the items of the module as well. + let name = item.name.as_ref().unwrap().to_string(); + if name.is_empty() { + panic!("Unexpected module with empty name"); + } + + cx.mod_item_in(&item, &name, &cache)?; + let module = match item.inner { + clean::StrippedItem(box clean::ModuleItem(m)) | clean::ModuleItem(m) => m, + _ => unreachable!(), + }; + for it in module.items { + info!("Adding {:?} to worklist", it.name); + work.push((cx.clone(), it)); + } + + cx.mod_item_out(&name)?; + } else if item.name.is_some() { + cx.item(item, &cache)?; + } + } + + renderer.after_krate(&krate, &cache)?; + renderer.after_run(diag) + } +} diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 0d8284029af..699f8c36cba 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -11,13 +11,15 @@ use std::fmt; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; -use rustc_hir::def_id::DefId; +use rustc_span::def_id::DefId; use rustc_target::spec::abi::Abi; use crate::clean::{self, PrimitiveType}; +use crate::formats::cache::cache; +use crate::formats::item_type::ItemType; use crate::html::escape::Escape; -use crate::html::item_type::ItemType; -use crate::html::render::{self, cache, CURRENT_DEPTH}; +use crate::html::render::cache::ExternalLocation; +use crate::html::render::CURRENT_DEPTH; pub trait Print { fn print(self, buffer: &mut Buffer); @@ -493,9 +495,9 @@ pub fn href(did: DefId) -> Option<(String, ItemType, Vec)> { fqp, shortty, match cache.extern_locations[&did.krate] { - (.., render::Remote(ref s)) => s.to_string(), - (.., render::Local) => "../".repeat(depth), - (.., render::Unknown) => return None, + (.., ExternalLocation::Remote(ref s)) => s.to_string(), + (.., ExternalLocation::Local) => "../".repeat(depth), + (.., ExternalLocation::Unknown) => return None, }, ) } @@ -574,12 +576,12 @@ fn primitive_link( } Some(&def_id) => { let loc = match m.extern_locations[&def_id.krate] { - (ref cname, _, render::Remote(ref s)) => Some((cname, s.to_string())), - (ref cname, _, render::Local) => { + (ref cname, _, ExternalLocation::Remote(ref s)) => Some((cname, s.to_string())), + (ref cname, _, ExternalLocation::Local) => { let len = CURRENT_DEPTH.with(|s| s.get()); Some((cname, "../".repeat(len))) } - (.., render::Unknown) => None, + (.., ExternalLocation::Unknown) => None, }; if let Some((cname, root)) = loc { write!( diff --git a/src/librustdoc/html/item_type.rs b/src/librustdoc/html/item_type.rs deleted file mode 100644 index cc78b4682d2..00000000000 --- a/src/librustdoc/html/item_type.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! Item types. - -use std::fmt; - -use serde::{Serialize, Serializer}; - -use rustc_span::hygiene::MacroKind; - -use crate::clean; - -/// Item type. Corresponds to `clean::ItemEnum` variants. -/// -/// The search index uses item types encoded as smaller numbers which equal to -/// discriminants. JavaScript then is used to decode them into the original value. -/// Consequently, every change to this type should be synchronized to -/// the `itemTypes` mapping table in `static/main.js`. -/// -/// In addition, code in `html::render` uses this enum to generate CSS classes, page prefixes, and -/// module headings. If you are adding to this enum and want to ensure that the sidebar also prints -/// a heading, edit the listing in `html/render.rs`, function `sidebar_module`. This uses an -/// ordering based on a helper function inside `item_module`, in the same file. -#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord)] -pub enum ItemType { - Module = 0, - ExternCrate = 1, - Import = 2, - Struct = 3, - Enum = 4, - Function = 5, - Typedef = 6, - Static = 7, - Trait = 8, - Impl = 9, - TyMethod = 10, - Method = 11, - StructField = 12, - Variant = 13, - Macro = 14, - Primitive = 15, - AssocType = 16, - Constant = 17, - AssocConst = 18, - Union = 19, - ForeignType = 20, - Keyword = 21, - OpaqueTy = 22, - ProcAttribute = 23, - ProcDerive = 24, - TraitAlias = 25, -} - -impl Serialize for ItemType { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (*self as u8).serialize(serializer) - } -} - -impl<'a> From<&'a clean::Item> for ItemType { - fn from(item: &'a clean::Item) -> ItemType { - let inner = match item.inner { - clean::StrippedItem(box ref item) => item, - ref inner => inner, - }; - - match *inner { - clean::ModuleItem(..) => ItemType::Module, - clean::ExternCrateItem(..) => ItemType::ExternCrate, - clean::ImportItem(..) => ItemType::Import, - clean::StructItem(..) => ItemType::Struct, - clean::UnionItem(..) => ItemType::Union, - clean::EnumItem(..) => ItemType::Enum, - clean::FunctionItem(..) => ItemType::Function, - clean::TypedefItem(..) => ItemType::Typedef, - clean::OpaqueTyItem(..) => ItemType::OpaqueTy, - clean::StaticItem(..) => ItemType::Static, - clean::ConstantItem(..) => ItemType::Constant, - clean::TraitItem(..) => ItemType::Trait, - clean::ImplItem(..) => ItemType::Impl, - clean::TyMethodItem(..) => ItemType::TyMethod, - clean::MethodItem(..) => ItemType::Method, - clean::StructFieldItem(..) => ItemType::StructField, - clean::VariantItem(..) => ItemType::Variant, - clean::ForeignFunctionItem(..) => ItemType::Function, // no ForeignFunction - clean::ForeignStaticItem(..) => ItemType::Static, // no ForeignStatic - clean::MacroItem(..) => ItemType::Macro, - clean::PrimitiveItem(..) => ItemType::Primitive, - clean::AssocConstItem(..) => ItemType::AssocConst, - clean::AssocTypeItem(..) => ItemType::AssocType, - clean::ForeignTypeItem => ItemType::ForeignType, - clean::KeywordItem(..) => ItemType::Keyword, - clean::TraitAliasItem(..) => ItemType::TraitAlias, - clean::ProcMacroItem(ref mac) => match mac.kind { - MacroKind::Bang => ItemType::Macro, - MacroKind::Attr => ItemType::ProcAttribute, - MacroKind::Derive => ItemType::ProcDerive, - }, - clean::StrippedItem(..) => unreachable!(), - } - } -} - -impl From for ItemType { - fn from(kind: clean::TypeKind) -> ItemType { - match kind { - clean::TypeKind::Struct => ItemType::Struct, - clean::TypeKind::Union => ItemType::Union, - clean::TypeKind::Enum => ItemType::Enum, - clean::TypeKind::Function => ItemType::Function, - clean::TypeKind::Trait => ItemType::Trait, - clean::TypeKind::Module => ItemType::Module, - clean::TypeKind::Static => ItemType::Static, - clean::TypeKind::Const => ItemType::Constant, - clean::TypeKind::Typedef => ItemType::Typedef, - clean::TypeKind::Foreign => ItemType::ForeignType, - clean::TypeKind::Macro => ItemType::Macro, - clean::TypeKind::Attr => ItemType::ProcAttribute, - clean::TypeKind::Derive => ItemType::ProcDerive, - clean::TypeKind::TraitAlias => ItemType::TraitAlias, - } - } -} - -impl ItemType { - pub fn as_str(&self) -> &'static str { - match *self { - ItemType::Module => "mod", - ItemType::ExternCrate => "externcrate", - ItemType::Import => "import", - ItemType::Struct => "struct", - ItemType::Union => "union", - ItemType::Enum => "enum", - ItemType::Function => "fn", - ItemType::Typedef => "type", - ItemType::Static => "static", - ItemType::Trait => "trait", - ItemType::Impl => "impl", - ItemType::TyMethod => "tymethod", - ItemType::Method => "method", - ItemType::StructField => "structfield", - ItemType::Variant => "variant", - ItemType::Macro => "macro", - ItemType::Primitive => "primitive", - ItemType::AssocType => "associatedtype", - ItemType::Constant => "constant", - ItemType::AssocConst => "associatedconstant", - ItemType::ForeignType => "foreigntype", - ItemType::Keyword => "keyword", - ItemType::OpaqueTy => "opaque", - ItemType::ProcAttribute => "attr", - ItemType::ProcDerive => "derive", - ItemType::TraitAlias => "traitalias", - } - } -} - -impl fmt::Display for ItemType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} diff --git a/src/librustdoc/html/mod.rs b/src/librustdoc/html/mod.rs new file mode 100644 index 00000000000..367538d440e --- /dev/null +++ b/src/librustdoc/html/mod.rs @@ -0,0 +1,9 @@ +crate mod escape; +crate mod format; +crate mod highlight; +crate mod layout; +pub mod markdown; +pub mod render; +crate mod sources; +crate mod static_files; +crate mod toc; diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs deleted file mode 100644 index fd32ba66d91..00000000000 --- a/src/librustdoc/html/render.rs +++ /dev/null @@ -1,4599 +0,0 @@ -// 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. - -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::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, ErrorStorage, PathError}; -use crate::doctree; -use crate::error::Error; -use crate::formats::{FormatRenderer, Renderer}; -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::item_type::ItemType; -use crate::html::markdown::{self, ErrorCodes, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine}; -use crate::html::sources; -use crate::html::{highlight, layout, static_files}; - -#[cfg(test)] -mod tests; - -mod cache; - -use cache::Cache; -crate use cache::ExternalLocation::{self, *}; - -/// A pair of name and its optional document. -pub type NameDoc = (String, Option); - -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, - /// 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>, - pub shared: Arc, - pub cache: Arc, - pub parent: Rc>, - all: Rc>, - pub errors: Arc, -} - -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, - /// 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, - /// 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>, - /// 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, - /// 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, - /// 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, -} - -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> { - if self.collapsed { - item.collapsed_doc_value().map(|s| s.into()) - } else { - item.doc_value().map(|s| s.into()) - } - } -} - -/// Metadata about implementations for a type or trait. -#[derive(Clone, Debug)] -pub struct Impl { - pub impl_item: clean::Item, -} - -impl Impl { - fn inner_impl(&self) -> &clean::Impl { - match self.impl_item.inner { - clean::ImplItem(ref impl_) => impl_, - _ => panic!("non-impl item found in impl"), - } - } - - fn trait_did(&self) -> Option { - self.inner_impl().trait_.def_id() - } -} - -// 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)] -struct IndexItem { - ty: ItemType, - name: String, - path: String, - desc: String, - parent: Option, - parent_idx: Option, - search_type: Option, -} - -impl Serialize for IndexItem { - fn serialize(&self, serializer: S) -> Result - 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)] -struct RenderType { - ty: Option, - idx: Option, - name: Option, - generics: Option>, -} - -impl Serialize for RenderType { - fn serialize(&self, serializer: S) -> Result - 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)] -struct Generic { - name: String, - defid: Option, - idx: Option, -} - -impl Serialize for Generic { - fn serialize(&self, serializer: S) -> Result - 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)] -struct IndexItemFunctionType { - inputs: Vec, - output: Option>, -} - -impl Serialize for IndexItemFunctionType { - fn serialize(&self, serializer: S) -> Result - 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)] -pub 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(&self, serializer: S) -> Result - 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!(static CACHE_KEY: RefCell> = Default::default()); -thread_local!(pub static CURRENT_DEPTH: Cell = Cell::new(0)); - -pub fn initial_ids() -> Vec { - [ - "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() -} - -impl FormatRenderer for Context { - type Output = Self; - - /// Generates the documentation for `crate` into the directory `dst` - fn init( - mut krate: clean::Crate, - options: RenderOptions, - renderinfo: RenderInfo, - _diag: &rustc_errors::Handler, - edition: Edition, - parent: Rc>, - ) -> 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, - extern_html_root_urls, - resource_suffix, - static_root_path, - generate_search_filter, - document_private, - .. - } = 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(), - }; - let errors = Arc::new(ErrorStorage::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 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(&errors), - 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)?; - let (new_crate, index, cache) = - Cache::from_krate(renderinfo, document_private, &extern_html_root_urls, &dst, krate); - krate = new_crate; - 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), - cache: cache.clone(), - parent, - all: Rc::new(RefCell::new(AllTypes::new())), - errors, - }; - - // Freeze the cache now that the index has been built. Put an Arc into TLS - // for future parallelization opportunities - CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); - 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)?; - 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> { - let nb_errors = - Arc::get_mut(&mut self.errors).map_or_else(|| 0, |errors| errors.write_errors(diag)); - 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) -> 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) = self.cache.crate_version { - format!( - "

Crate {}

\ -
\ -

Version {}

\ -
\ -

Back to index

", - 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 = "

Settings

"; - 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, - module: &clean::Module, - ) -> 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); - // 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 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) -> 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) -> 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); - // 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, -) -> 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 = 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, Vec)> { - 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, Vec)> { - 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, - elems: FxHashSet, - } - - 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::>(); - files.sort_unstable_by(|a, b| a.cmp(b)); - let subs = subs.iter().map(|s| s.to_json_string()).collect::>().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!( - "

\ - List of all crates\ -

    {}
", - krates - .iter() - .map(|s| { - format!("
  • {}
  • ", ensure_trailing_slash(s), s) - }) - .collect::() - ); - 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 &cx.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 cx.cache.paths.get(&did) { - Some(p) => p, - None => match cx.cache.external_paths.get(&did) { - Some(p) => p, - None => continue, - }, - }; - - #[derive(Serialize)] - struct Implementor { - text: String, - synthetic: bool, - types: Vec, - } - - 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::>(); - - // 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() && !cx.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, "{}", 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, - enums: FxHashSet, - unions: FxHashSet, - primitives: FxHashSet, - traits: FxHashSet, - macros: FxHashSet, - functions: FxHashSet, - typedefs: FxHashSet, - opaque_tys: FxHashSet, - statics: FxHashSet, - constants: FxHashSet, - keywords: FxHashSet, - attributes: FxHashSet, - derives: FxHashSet, - trait_aliases: FxHashSet, -} - -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, title: &str, class: &str) { - if !e.is_empty() { - let mut e: Vec<&ItemEntry> = e.iter().collect(); - e.sort(); - write!( - f, - "

    {}

      {}
    ", - title, - Escape(title), - class, - e.iter().map(|s| format!("
  • {}
  • ", s.print())).collect::() - ); - } -} - -impl AllTypes { - fn print(self, f: &mut Buffer) { - write!( - f, - "

    \ - \ - \ - \ - []\ - \ - - - List of all items\ -

    " - ); - 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 }, - 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!( - "
    \ -
    {}
    \ -
    {}
    -
    ", - description, - sub_settings.iter().map(|s| s.display()).collect::() - ), - Setting::Entry { ref js_data_name, ref description, ref default_value } => format!( - "
    \ - \ -
    {}
    \ -
    ", - 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> From<(&'static str, Vec)> for Setting { - fn from(values: (&'static str, Vec)) -> Setting { - Setting::Section { - description: values.0, - sub_settings: values.1.into_iter().map(|v| v.into()).collect::>(), - } - } -} - -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!( - "

    \ - Rustdoc settings\ -

    \ -
    {}
    \ -", - settings.iter().map(|s| s.display()).collect::(), - 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) -> 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), - |buf: &mut _| print_item(self, it, buf), - &self.shared.style_files, - ) - } else { - let mut url = self.root_path(); - if let Some(&(ref names, ty)) = self.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> { - // 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 - } -} - -impl Context { - /// 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) -> Option { - 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 *self.cache.extern_locations.get(&item.source.cnum)? { - (ref name, ref src, Local) => (name, src), - (ref name, ref src, Remote(ref s)) => { - root = s.to_string(); - (name, src) - } - (_, _, 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(w: &mut Buffer, f: F) -where - F: FnOnce(&mut Buffer), -{ - write!(w, "
    "); - f(w); - write!(w, "
    ") -} - -fn print_item(cx: &Context, item: &clean::Item, buf: &mut Buffer) { - debug_assert!(!item.is_stripped()); - // Write the breadcrumb trail header for the top - write!(buf, "

    "); - if let Some(version) = item.stable_since() { - write!( - buf, - "{0}", - version - ); - } - write!( - buf, - "\ - \ - []\ - \ - " - ); - - // 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) { - write!(buf, "[src]", l, "goto source code"); - } - } - - write!(buf, ""); // out-of-band - write!(buf, ""); - 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, - "{}::", - "../".repeat(cur.len() - i - 1), - component - ); - } - } - write!(buf, "{}", item.type_(), item.name.as_ref().unwrap()); - - write!(buf, "

    "); // 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), - clean::StructItem(ref s) => item_struct(buf, cx, item, s), - clean::UnionItem(ref s) => item_union(buf, cx, item, s), - clean::EnumItem(ref e) => item_enum(buf, cx, item, e), - clean::TypedefItem(ref t, _) => item_typedef(buf, cx, item, t), - 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), - 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), - clean::KeywordItem(_) => item_keyword(buf, cx, item), - clean::OpaqueTyItem(ref e, _) => item_opaque_ty(buf, cx, item, e), - clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta), - _ => { - // 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] -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[..]) -} - -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::>() - .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, - "
    {}{}
    ", - 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, - "
    {}
    ", - 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, - "
    {}
    ", - 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, "
    ", if is_hidden { " hidden" } else { "" }); - for stability in stabilities { - write!(w, "{}", stability); - } - write!(w, "
    "); - } -} - -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, "
    ", { - 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 Struct {{ .. }} syntax; cannot be \ - matched against without a wildcard ..; 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, "
    "); - } -} - -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::>(); - - // 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, ""); - } - curty = myty; - let (short, name) = item_ty_to_strs(&myty.unwrap()); - write!( - w, - "

    \ - {name}

    \n", - 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, - ""); - } - - clean::ImportItem(ref import) => { - write!( - w, - "", - 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 => - { - "âš " - } - _ => "", - }; - - let stab = myitem.stability_class(); - let add = if stab.is_some() { " " } else { "" }; - - let doc_value = myitem.doc_value().unwrap_or(""); - write!( - w, - "\ - \ - \ - \ - ", - 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::>() - .join(" "), - ); - } - } - } - - if curty.is_some() { - write!(w, "
    {}extern crate {} as {};", - myitem.visibility.print_with_space(), - anchor(myitem.def_id, src), - name - ), - None => write!( - w, - "
    {}extern crate {};", - myitem.visibility.print_with_space(), - anchor(myitem.def_id, name) - ), - } - write!(w, "
    {}{}
    {name}{unsafety_flag}{stab_tags}{docs}
    "); - } -} - -/// 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#"{}"#, 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 { - 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!( - "
    👎 {}
    ", - 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 = - "🔬 This is a nightly-only experimental API.".to_owned(); - - if let Some(feature) = stab.feature.as_deref() { - let mut feature = format!("{}", Escape(&feature)); - if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, stab.issue) { - feature.push_str(&format!( - " #{issue}", - 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!( - "
    {}{}
    ", - message, - MarkdownHtml( - &unstable_reason, - &mut ids, - error_codes, - cx.shared.edition, - &cx.shared.playground, - ) - .into_string() - ); - } - - stability.push(format!("
    {}
    ", message)); - } - - if let Some(ref cfg) = item.attrs.cfg { - stability.push(format!("
    {}
    ", cfg.render_long_html())); - } - - stability -} - -fn item_constant(w: &mut Buffer, cx: &Context, it: &clean::Item, c: &clean::Constant) { - write!(w, "
    ");
    -    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, "
    "); - document(w, cx, it) -} - -fn item_static(w: &mut Buffer, cx: &Context, it: &clean::Item, s: &clean::Static) { - write!(w, "
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "{vis}static {mutability}\
    -               {name}: {typ}
    ", - 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, "
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "{vis}{constness}{asyncness}{unsafety}{abi}fn \
    -           {name}{generics}{decl}{spotlight}{where_clause}
    ", - 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], -) { - // 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, - ); -} - -fn render_impls(cx: &Context, w: &mut Buffer, traits: &[&&Impl], containing_item: &clean::Item) { - 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, - &[], - ); - buffer.into_inner() - }) - .collect::>(); - 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) { - let bounds = bounds(&t.bounds, false); - let types = t.items.iter().filter(|m| m.is_associated_type()).collect::>(); - let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::>(); - let required = t.items.iter().filter(|m| m.is_ty_method()).collect::>(); - let provided = t.items.iter().filter(|m| m.is_method()).collect::>(); - - // Output the trait definition - wrap_into_docblock(w, |w| { - write!(w, "
    ");
    -        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, "
    "); - } - } - 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, "
    "); - } - } - write!(w, "}}"); - } - write!(w, "
    ") - }); - - // Trait documentation - document(w, cx, it); - - fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { - write!( - w, - " -

    \ - {1}\ -

    {2}", - id, title, extra_content - ) - } - - fn write_loading_content(w: &mut Buffer, extra_content: &str) { - write!(w, "{}Loading content...", 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, "

    ", id = id,); - render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl); - write!(w, ""); - render_stability_since(w, m, t); - write!(w, "

    "); - document(w, cx, m); - } - - if !types.is_empty() { - write_small_section_header( - w, - "associated-types", - "Associated Types", - "
    ", - ); - for t in &types { - trait_item(w, cx, *t, it); - } - write_loading_content(w, "
    "); - } - - if !consts.is_empty() { - write_small_section_header( - w, - "associated-const", - "Associated Constants", - "
    ", - ); - for t in &consts { - trait_item(w, cx, *t, it); - } - write_loading_content(w, "
    "); - } - - // Output the documentation for each function individually - if !required.is_empty() { - write_small_section_header( - w, - "required-methods", - "Required methods", - "
    ", - ); - for m in &required { - trait_item(w, cx, *m, it); - } - write_loading_content(w, "
    "); - } - if !provided.is_empty() { - write_small_section_header( - w, - "provided-methods", - "Provided methods", - "
    ", - ); - for m in &provided { - trait_item(w, cx, *m, it); - } - write_loading_content(w, "
    "); - } - - // If there are methods directly on this trait object, render them here. - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All); - - if let Some(implementors) = cx.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::, _>(|i| { - i.inner_impl().for_.def_id().map_or(true, |d| cx.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, - &[], - ); - } - write_loading_content(w, ""); - } - - write_small_section_header( - w, - "implementors", - "Implementors", - "
    ", - ); - for implementor in concrete { - render_implementor(cx, implementor, w, &implementor_dups, &[]); - } - write_loading_content(w, "
    "); - - if t.auto { - write_small_section_header( - w, - "synthetic-implementors", - "Auto implementors", - "
    ", - ); - for implementor in synthetic { - render_implementor( - cx, - implementor, - w, - &implementor_dups, - &collect_paths_for_type(implementor.inner_impl().for_.clone()), - ); - } - write_loading_content(w, "
    "); - } - } 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", - "
    ", - ); - write_loading_content(w, "
    "); - - if t.auto { - write_small_section_header( - w, - "synthetic-implementors", - "Auto implementors", - "
    ", - ); - write_loading_content(w, "
    "); - } - } - - write!( - w, - "", - root_path = vec![".."; cx.current.len()].join("/"), - path = if it.def_id.is_local() { - cx.current.join("/") - } else { - let (ref path, _) = cx.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::html::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 {}: {}", - 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 {}", - 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, "{0}", 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 {name}\ - {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) { - wrap_into_docblock(w, |w| { - write!(w, "
    ");
    -        render_attributes(w, it, true);
    -        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true);
    -        write!(w, "
    ") - }); - - 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, - "

    - Fields{}

    ", - 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, - "\ - \ - {name}: {ty}\ - ", - 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) -} - -fn item_union(w: &mut Buffer, cx: &Context, it: &clean::Item, s: &clean::Union) { - wrap_into_docblock(w, |w| { - write!(w, "
    ");
    -        render_attributes(w, it, true);
    -        render_union(w, it, Some(&s.generics), &s.fields, "", true);
    -        write!(w, "
    ") - }); - - 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, - "

    - Fields

    " - ); - for (field, ty) in fields { - let name = field.name.as_ref().expect("union field name"); - let id = format!("{}.{}", ItemType::StructField, name); - write!( - w, - "\ - \ - {name}: {ty}\ - ", - id = id, - name = name, - shortty = ItemType::StructField, - ty = ty.print() - ); - if let Some(stability_class) = field.stability_class() { - write!(w, "", stab = stability_class); - } - document(w, cx, field); - } - } - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -fn item_enum(w: &mut Buffer, cx: &Context, it: &clean::Item, e: &clean::Enum) { - wrap_into_docblock(w, |w| { - write!(w, "
    ");
    -        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, "
    ") - }); - - document(w, cx, it); - if !e.variants.is_empty() { - write!( - w, - "

    - Variants{}

    \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, - "
    \ - \ - {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, "
    "); - 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, "
    ", id = variant_id); - write!( - w, - "

    Fields of {name}

    ", - 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, - "\ - \ - {f}: {t}\ - ", - id = id, - f = field.name.as_ref().unwrap(), - t = ty.print() - ); - document(w, cx, field); - } - } - write!(w, "
    "); - } - render_stability_since(w, variant, it); - } - } - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -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, - "{}", - 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), -} - -impl<'a> AssocItemLink<'a> { - fn anchor(&self, id: &'a String) -> Self { - match *self { - AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(&id)), - ref other => *other, - } - } -} - -enum AssocItemRender<'a> { - All, - DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type, deref_mut_: bool }, -} - -#[derive(Copy, Clone, PartialEq)] -enum RenderMode { - Normal, - ForDeref { mut_: bool }, -} - -fn render_assoc_items( - w: &mut Buffer, - cx: &Context, - containing_item: &clean::Item, - it: DefId, - what: AssocItemRender<'_>, -) { - let c = &cx.cache; - let v = match c.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, - "\ -

    \ - Implementations\ -

    \ - " - ); - RenderMode::Normal - } - AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => { - write!( - w, - "\ -

    \ - Methods from {}<Target = {}>\ - \ -

    \ - ", - 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, - &[], - ); - } - } - if let AssocItemRender::DerefFor { .. } = what { - return; - } - if !traits.is_empty() { - let deref_impl = - traits.iter().find(|t| t.inner_impl().trait_.def_id() == c.deref_trait_did); - if let Some(impl_) = deref_impl { - let has_deref_mut = - traits.iter().any(|t| t.inner_impl().trait_.def_id() == c.deref_mut_trait_did); - render_deref_methods(w, cx, impl_, containing_item, has_deref_mut); - } - - let (synthetic, concrete): (Vec<&&Impl>, Vec<&&Impl>) = - traits.iter().partition(|t| t.inner_impl().synthetic); - let (blanket_impl, concrete): (Vec<&&Impl>, _) = - concrete.into_iter().partition(|t| t.inner_impl().blanket_impl.is_some()); - - let mut impls = Buffer::empty_from(&w); - render_impls(cx, &mut impls, &concrete, containing_item); - let impls = impls.into_inner(); - if !impls.is_empty() { - write!( - w, - "\ -

    \ - Trait Implementations\ -

    \ -
    {}
    ", - impls - ); - } - - if !synthetic.is_empty() { - write!( - w, - "\ -

    \ - Auto Trait Implementations\ - \ -

    \ -
    \ - " - ); - render_impls(cx, w, &synthetic, containing_item); - write!(w, "
    "); - } - - if !blanket_impl.is_empty() { - write!( - w, - "\ -

    \ - Blanket Implementations\ - \ -

    \ -
    \ - " - ); - render_impls(cx, w, &blanket_impl, containing_item); - write!(w, "
    "); - } - } -} - -fn render_deref_methods( - w: &mut Buffer, - cx: &Context, - impl_: &Impl, - container_item: &clean::Item, - deref_mut: bool, -) { - let deref_type = impl_.inner_impl().trait_.as_ref().unwrap(); - let (target, real_target) = impl_ - .inner_impl() - .items - .iter() - .find_map(|item| match item.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); - } else { - if let Some(prim) = target.primitive_type() { - if let Some(&did) = cx.cache.primitive_locations.get(&prim) { - render_assoc_items(w, cx, container_item, did, what); - } - } - } -} - -fn should_render_item(item: &clean::Item, deref_mut_: bool) -> 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!( - "

    Important traits for {}

    \ - ", - impl_.for_.print() - )); - trait_.push_str(&impl_.for_.print().to_string()); - } - - //use the "where" class here to make it small - out.push_str(&format!( - "{}", - 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(" "); - assoc_type( - &mut out, - it, - &[], - Some(&tydef.type_), - AssocItemLink::GotoSource(t_did, &FxHashSet::default()), - "", - ); - out.push_str(";"); - } - } - } - } - } - } - - if !out.is_empty() { - out.insert_str( - 0, - "ⓘ
    " - - ); - out.push_str("
    "); - } - - 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, - 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], -) { - 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, "

    ", 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, " "); - assoc_type(w, it, &[], Some(&tydef.type_), AssocItemLink::Anchor(None), ""); - write!(w, ";"); - } - } - } - write!(w, ""); - } else { - write!( - w, - "

    {}", - id, - aliases, - i.inner_impl().print() - ); - } - write!(w, "", 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) { - write!(w, "[src]", l, "goto source code"); - } - write!(w, "

    "); - if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) { - let mut ids = cx.id_map.borrow_mut(); - write!( - w, - "
    {}
    ", - 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, - ) { - 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, "

    ", id, item_type, extra_class); - write!(w, ""); - render_assoc_item(w, item, link.anchor(&id), ItemType::Impl); - write!(w, ""); - render_stability_since_raw(w, item.stable_since(), outer_version); - if let Some(l) = cx.src_href(item) { - write!( - w, - "[src]", - l, "goto source code" - ); - } - write!(w, "

    "); - } - } - clean::TypedefItem(ref tydef, _) => { - let id = cx.derive_id(format!("{}.{}", ItemType::AssocType, name)); - write!(w, "

    ", id, item_type, extra_class); - assoc_type(w, item, &Vec::new(), Some(&tydef.type_), link.anchor(&id), ""); - write!(w, "

    "); - } - clean::AssocConstItem(ref ty, ref default) => { - let id = cx.derive_id(format!("{}.{}", item_type, name)); - write!(w, "

    ", id, item_type, extra_class); - assoc_const(w, item, ty, default.as_ref(), link.anchor(&id), ""); - write!(w, ""); - render_stability_since_raw(w, item.stable_since(), outer_version); - if let Some(l) = cx.src_href(item) { - write!( - w, - "[src]", - l, "goto source code" - ); - } - write!(w, "

    "); - } - clean::AssocTypeItem(ref bounds, ref default) => { - let id = cx.derive_id(format!("{}.{}", item_type, name)); - write!(w, "

    ", id, item_type, extra_class); - assoc_type(w, item, bounds, default.as_ref(), link.anchor(&id), ""); - write!(w, "

    "); - } - 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 = &cx.cache.traits; - let trait_ = i.trait_did().map(|did| &traits[&did]); - - write!(w, "
    "); - 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, - ); - } - - 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, - ) { - 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, - ); - } - } - - // 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, - ); - } - } - write!(w, "
    "); -} - -fn item_opaque_ty(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::OpaqueTy) { - write!(w, "
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "type {}{}{where_clause} = impl {bounds};
    ", - 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) -} - -fn item_trait_alias(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::TraitAlias) { - write!(w, "
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "trait {}{}{} = {};
    ", - 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) -} - -fn item_typedef(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Typedef) { - write!(w, "
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "type {}{}{where_clause} = {type_};
    ", - 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) -} - -fn item_foreign_type(w: &mut Buffer, cx: &Context, it: &clean::Item) { - writeln!(w, "
    extern {{");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "    {}type {};\n}}
    ", - 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) -} - -fn print_sidebar(cx: &Context, it: &clean::Item, buffer: &mut Buffer) { - let parentlen = cx.current.len() - if it.is_mod() { 1 } else { 0 }; - - if it.is_struct() - || it.is_trait() - || it.is_primitive() - || it.is_union() - || it.is_enum() - || it.is_mod() - || it.is_typedef() - { - write!( - buffer, - "

    {}{}

    ", - 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) = cx.cache.crate_version { - write!( - buffer, - "
    \ -

    Version {}

    \ -
    ", - Escape(version) - ); - } - } - - write!(buffer, "
    "); - if it.is_crate() { - write!( - buffer, - "

    See all {}'s items

    ", - 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, "

    "); - for (i, name) in cx.current.iter().take(parentlen).enumerate() { - if i > 0 { - write!(buffer, "::"); - } - write!( - buffer, - "{}", - &cx.root_path()[..(cx.current.len() - i - 1) * 3], - *name - ); - } - write!(buffer, "

    "); - - // Sidebar refers to the enclosing module, not this module. - let relpath = if it.is_mod() { "../" } else { "" }; - write!( - buffer, - "", - 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, "", path = relpath); - } - // Closes sidebar-elems div. - write!(buffer, "
    "); -} - -fn get_next_url(used_links: &mut FxHashSet, 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, - deref_mut: bool, -) -> Vec { - 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!( - "{}", - get_next_url(used_links, format!("method.{}", name)), - name - )) - } else { - None - } - } - _ => None, - }) - .collect::>() -} - -// 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::>(); - if !ret.is_empty() { - // We want links' order to be reproducible so we don't use unstable sort. - ret.sort(); - out.push_str(&format!( - "Methods\ -
    {}
    ", - 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(""); - 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(""); - 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::>(); - // We want links' order to be reproducible so we don't use unstable sort. - ret.sort(); - if !ret.is_empty() { - out.push_str(&format!( - "
    {}
    ", - 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!( - "{}{}", - encoded, - if is_negative_impl { "!" } else { "" }, - out - ); - if links.insert(generated.clone()) { Some(generated) } else { None } - } else { - None - } - }) - .collect::>(); - ret.sort(); - ret.join("") - }; - - let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = - v.iter().partition::, _>(|i| i.inner_impl().synthetic); - let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = concrete - .into_iter() - .partition::, _>(|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( - "\ - Trait Implementations", - ); - out.push_str(&format!("
    {}
    ", concrete_format)); - } - - if !synthetic_format.is_empty() { - out.push_str( - "\ - Auto Trait Implementations", - ); - out.push_str(&format!("
    {}
    ", synthetic_format)); - } - - if !blanket_format.is_empty() { - out.push_str( - "\ - Blanket Implementations", - ); - out.push_str(&format!("
    {}
    ", 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!( - "Fields\ -
    {}
    ", - fields - )); - } - } - - sidebar.push_str(&sidebar_assoc_items(it)); - - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", 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!("{name}", name = name)) - } - _ => None, - }) - .collect::>(); - let mut consts = t - .items - .iter() - .filter_map(|m| match m.name { - Some(ref name) if m.is_associated_const() => { - Some(format!("{name}", name = name)) - } - _ => None, - }) - .collect::>(); - let mut required = t - .items - .iter() - .filter_map(|m| match m.name { - Some(ref name) if m.is_ty_method() => { - Some(format!("{name}", name = name)) - } - _ => None, - }) - .collect::>(); - let mut provided = t - .items - .iter() - .filter_map(|m| match m.name { - Some(ref name) if m.is_method() => { - Some(format!("{0}", name)) - } - _ => None, - }) - .collect::>(); - - if !types.is_empty() { - types.sort(); - sidebar.push_str(&format!( - "\ - Associated Types
    {}
    ", - types.join("") - )); - } - if !consts.is_empty() { - consts.sort(); - sidebar.push_str(&format!( - "\ - Associated Constants
    {}
    ", - consts.join("") - )); - } - if !required.is_empty() { - required.sort(); - sidebar.push_str(&format!( - "\ - Required Methods
    {}
    ", - required.join("") - )); - } - if !provided.is_empty() { - provided.sort(); - sidebar.push_str(&format!( - "\ - Provided Methods
    {}
    ", - 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::>(); - - if !res.is_empty() { - res.sort(); - sidebar.push_str(&format!( - "\ - Implementations on Foreign Types
    {}
    ", - res.into_iter() - .map(|(name, id)| format!("{}", id, Escape(&name))) - .collect::>() - .join("") - )); - } - } - - sidebar.push_str(&sidebar_assoc_items(it)); - - sidebar.push_str("Implementors"); - if t.auto { - sidebar.push_str( - "Auto Implementors", - ); - } - - write!(buf, "
    {}
    ", sidebar) -} - -fn sidebar_primitive(buf: &mut Buffer, it: &clean::Item) { - let sidebar = sidebar_assoc_items(it); - - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", sidebar); - } -} - -fn sidebar_typedef(buf: &mut Buffer, it: &clean::Item) { - let sidebar = sidebar_assoc_items(it); - - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", 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!("{name}", name = name)) - } - _ => None, - }) - .collect::>(); - 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!( - "Fields\ -
    {}
    ", - fields - )); - } - - sidebar.push_str(&sidebar_assoc_items(it)); - - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", 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!("{name}", name = name)), - _ => None, - }) - .collect::>(); - if !variants.is_empty() { - variants.sort_unstable(); - sidebar.push_str(&format!( - "Variants\ -
    {}
    ", - variants.join(""), - )); - } - - sidebar.push_str(&sidebar_assoc_items(it)); - - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", 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!( - "
  • {name}
  • ", - 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!( - "
  • {name}
  • ", - id = short, - name = name - )); - } - } - - if !sidebar.is_empty() { - write!(buf, "
      {}
    ", sidebar); - } -} - -fn sidebar_foreign_type(buf: &mut Buffer, it: &clean::Item) { - let sidebar = sidebar_assoc_items(it); - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", 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, "
    ");
    -            write!(w, "{}!() {{ /* proc-macro */ }}", name);
    -            write!(w, "
    "); - } - MacroKind::Attr => { - write!(w, "
    ");
    -            write!(w, "#[{}]", name);
    -            write!(w, "
    "); - } - MacroKind::Derive => { - write!(w, "
    ");
    -            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, "
    "); - } - } - document(w, cx, it) -} - -fn item_primitive(w: &mut Buffer, cx: &Context, it: &clean::Item) { - document(w, cx, it); - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -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 { - 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 -} - -crate fn cache() -> Arc { - CACHE_KEY.with(|c| c.borrow().clone()) -} diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs index 6da4a4628e8..378efa1a1be 100644 --- a/src/librustdoc/html/render/cache.rs +++ b/src/librustdoc/html/render/cache.rs @@ -1,19 +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, RenderType, TypeWithKind}; -use crate::config::RenderInfo; +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 { @@ -25,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>, - - /// 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, ItemType)>, - - /// Similar to `paths`, but only holds external paths. This is only used for - /// generating explicit hyperlinks to other crates. - pub external_paths: FxHashMap, 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>, - - /// 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, - - /// 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>, - - /// Cache of where external crate documentation can be found. - pub extern_locations: FxHashMap, - - /// Cache of where documentation for primitives can be found. - pub primitive_locations: FxHashMap, - - // 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, - - /// The version of the crate being documented, if given from the `--crate-version` flag. - pub crate_version: Option, - - /// 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, - parent_stack: Vec, - parent_is_trait_impl: bool, - search_index: Vec, - stripped_mod: bool, - pub deref_trait_did: Option, - pub deref_mut_trait_did: Option, - pub owned_box_did: Option, - masked_crates: FxHashSet, - - // 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, 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>, -} - -impl Cache { - pub fn from_krate( - renderinfo: RenderInfo, - document_private: bool, - extern_html_root_urls: &BTreeMap, - 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 { - 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, @@ -539,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![]; @@ -641,7 +164,7 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { ) } -fn get_index_search_type(item: &clean::Item) -> Option { +crate fn get_index_search_type(item: &clean::Item) -> Option { 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..1678cff16a9 --- /dev/null +++ b/src/librustdoc/html/render/mod.rs @@ -0,0 +1,4599 @@ +// 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::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, ErrorStorage, PathError}; +use crate::doctree; +use crate::error::Error; +use crate::formats::cache::{cache, Cache}; +use crate::formats::item_type::ItemType; +use crate::formats::{FormatRenderer, Impl}; +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); + +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, + /// 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>, + pub shared: Arc, + all: Rc>, + pub errors: Arc, +} + +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, + /// 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, + /// 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>, + /// 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, + /// 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, + /// 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, +} + +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> { + 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, + pub parent_idx: Option, + pub search_type: Option, +} + +impl Serialize for IndexItem { + fn serialize(&self, serializer: S) -> Result + 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)] +pub struct RenderType { + ty: Option, + idx: Option, + name: Option, + generics: Option>, +} + +impl Serialize for RenderType { + fn serialize(&self, serializer: S) -> Result + 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)] +pub struct Generic { + name: String, + defid: Option, + idx: Option, +} + +impl Serialize for Generic { + fn serialize(&self, serializer: S) -> Result + 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, + output: Option>, +} + +impl Serialize for IndexItemFunctionType { + fn serialize(&self, serializer: S) -> Result + 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)] +pub 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(&self, serializer: S) -> Result + 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 = Cell::new(0)); + +pub fn initial_ids() -> Vec { + [ + "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() +} + +impl FormatRenderer for Context { + type Output = Self; + + /// Generates the documentation for `crate` into the directory `dst` + fn init( + mut krate: clean::Crate, + options: RenderOptions, + _renderinfo: 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(), + }; + let errors = Arc::new(ErrorStorage::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 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(&errors), + 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, + }; + + 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> { + let nb_errors = + Arc::get_mut(&mut self.errors).map_or_else(|| 0, |errors| errors.write_errors(diag)); + 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!( + "

    Crate {}

    \ +
    \ +

    Version {}

    \ +
    \ +

    Back to index

    ", + 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 = "

    Settings

    "; + 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, _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 = 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, Vec)> { + 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, Vec)> { + 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, + elems: FxHashSet, + } + + 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::>(); + files.sort_unstable_by(|a, b| a.cmp(b)); + let subs = subs.iter().map(|s| s.to_json_string()).collect::>().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!( + "

    \ + List of all crates\ +

      {}
    ", + krates + .iter() + .map(|s| { + format!("
  • {}
  • ", ensure_trailing_slash(s), s) + }) + .collect::() + ); + 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, + } + + 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::>(); + + // 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, "{}", 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, + enums: FxHashSet, + unions: FxHashSet, + primitives: FxHashSet, + traits: FxHashSet, + macros: FxHashSet, + functions: FxHashSet, + typedefs: FxHashSet, + opaque_tys: FxHashSet, + statics: FxHashSet, + constants: FxHashSet, + keywords: FxHashSet, + attributes: FxHashSet, + derives: FxHashSet, + trait_aliases: FxHashSet, +} + +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, title: &str, class: &str) { + if !e.is_empty() { + let mut e: Vec<&ItemEntry> = e.iter().collect(); + e.sort(); + write!( + f, + "

    {}

      {}
    ", + title, + Escape(title), + class, + e.iter().map(|s| format!("
  • {}
  • ", s.print())).collect::() + ); + } +} + +impl AllTypes { + fn print(self, f: &mut Buffer) { + write!( + f, + "

    \ + \ + \ + \ + []\ + \ + + + List of all items\ +

    " + ); + 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 }, + 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!( + "
    \ +
    {}
    \ +
    {}
    +
    ", + description, + sub_settings.iter().map(|s| s.display()).collect::() + ), + Setting::Entry { ref js_data_name, ref description, ref default_value } => format!( + "
    \ + \ +
    {}
    \ +
    ", + 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> From<(&'static str, Vec)> for Setting { + fn from(values: (&'static str, Vec)) -> Setting { + Setting::Section { + description: values.0, + sub_settings: values.1.into_iter().map(|v| v.into()).collect::>(), + } + } +} + +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!( + "

    \ + Rustdoc settings\ +

    \ +
    {}
    \ +", + settings.iter().map(|s| s.display()).collect::(), + 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> { + // 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 { + 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(w: &mut Buffer, f: F) +where + F: FnOnce(&mut Buffer), +{ + write!(w, "
    "); + f(w); + write!(w, "
    ") +} + +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, "

    "); + if let Some(version) = item.stable_since() { + write!( + buf, + "{0}", + version + ); + } + write!( + buf, + "\ + \ + []\ + \ + " + ); + + // 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, "[src]", l, "goto source code"); + } + } + + write!(buf, ""); // out-of-band + write!(buf, ""); + 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, + "{}::", + "../".repeat(cur.len() - i - 1), + component + ); + } + } + write!(buf, "{}", item.type_(), item.name.as_ref().unwrap()); + + write!(buf, "

    "); // 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::>() + .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, + "
    {}{}
    ", + 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, + "
    {}
    ", + 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, + "
    {}
    ", + 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, "
    ", if is_hidden { " hidden" } else { "" }); + for stability in stabilities { + write!(w, "{}", stability); + } + write!(w, "
    "); + } +} + +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, "
    ", { + 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 Struct {{ .. }} syntax; cannot be \ + matched against without a wildcard ..; 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, "
    "); + } +} + +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::>(); + + // 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, ""); + } + curty = myty; + let (short, name) = item_ty_to_strs(&myty.unwrap()); + write!( + w, + "

    \ + {name}

    \n", + 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, + ""); + } + + clean::ImportItem(ref import) => { + write!( + w, + "", + 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 => + { + "âš " + } + _ => "", + }; + + let stab = myitem.stability_class(); + let add = if stab.is_some() { " " } else { "" }; + + let doc_value = myitem.doc_value().unwrap_or(""); + write!( + w, + "\ + \ + \ + \ + ", + 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::>() + .join(" "), + ); + } + } + } + + if curty.is_some() { + write!(w, "
    {}extern crate {} as {};", + myitem.visibility.print_with_space(), + anchor(myitem.def_id, src), + name + ), + None => write!( + w, + "
    {}extern crate {};", + myitem.visibility.print_with_space(), + anchor(myitem.def_id, name) + ), + } + write!(w, "
    {}{}
    {name}{unsafety_flag}{stab_tags}{docs}
    "); + } +} + +/// 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#"{}"#, 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 { + 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!( + "
    👎 {}
    ", + 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 = + "🔬 This is a nightly-only experimental API.".to_owned(); + + if let Some(feature) = stab.feature.as_deref() { + let mut feature = format!("{}", Escape(&feature)); + if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, stab.issue) { + feature.push_str(&format!( + " #{issue}", + 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!( + "
    {}{}
    ", + message, + MarkdownHtml( + &unstable_reason, + &mut ids, + error_codes, + cx.shared.edition, + &cx.shared.playground, + ) + .into_string() + ); + } + + stability.push(format!("
    {}
    ", message)); + } + + if let Some(ref cfg) = item.attrs.cfg { + stability.push(format!("
    {}
    ", cfg.render_long_html())); + } + + stability +} + +fn item_constant(w: &mut Buffer, cx: &Context, it: &clean::Item, c: &clean::Constant) { + write!(w, "
    ");
    +    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, "
    "); + document(w, cx, it) +} + +fn item_static(w: &mut Buffer, cx: &Context, it: &clean::Item, s: &clean::Static) { + write!(w, "
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "{vis}static {mutability}\
    +               {name}: {typ}
    ", + 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, "
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "{vis}{constness}{asyncness}{unsafety}{abi}fn \
    +           {name}{generics}{decl}{spotlight}{where_clause}
    ", + 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::>(); + 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::>(); + let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::>(); + let required = t.items.iter().filter(|m| m.is_ty_method()).collect::>(); + let provided = t.items.iter().filter(|m| m.is_method()).collect::>(); + + // Output the trait definition + wrap_into_docblock(w, |w| { + write!(w, "
    ");
    +        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, "
    "); + } + } + 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, "
    "); + } + } + write!(w, "}}"); + } + write!(w, "
    ") + }); + + // Trait documentation + document(w, cx, it); + + fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { + write!( + w, + " +

    \ + {1}\ +

    {2}", + id, title, extra_content + ) + } + + fn write_loading_content(w: &mut Buffer, extra_content: &str) { + write!(w, "{}Loading content...", 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, "

    ", id = id,); + render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl); + write!(w, ""); + render_stability_since(w, m, t); + write!(w, "

    "); + document(w, cx, m); + } + + if !types.is_empty() { + write_small_section_header( + w, + "associated-types", + "Associated Types", + "
    ", + ); + for t in &types { + trait_item(w, cx, *t, it); + } + write_loading_content(w, "
    "); + } + + if !consts.is_empty() { + write_small_section_header( + w, + "associated-const", + "Associated Constants", + "
    ", + ); + for t in &consts { + trait_item(w, cx, *t, it); + } + write_loading_content(w, "
    "); + } + + // Output the documentation for each function individually + if !required.is_empty() { + write_small_section_header( + w, + "required-methods", + "Required methods", + "
    ", + ); + for m in &required { + trait_item(w, cx, *m, it); + } + write_loading_content(w, "
    "); + } + if !provided.is_empty() { + write_small_section_header( + w, + "provided-methods", + "Provided methods", + "
    ", + ); + for m in &provided { + trait_item(w, cx, *m, it); + } + write_loading_content(w, "
    "); + } + + // 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::, _>(|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", + "
    ", + ); + for implementor in concrete { + render_implementor(cx, implementor, w, &implementor_dups, &[], cache); + } + write_loading_content(w, "
    "); + + if t.auto { + write_small_section_header( + w, + "synthetic-implementors", + "Auto implementors", + "
    ", + ); + 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, "
    "); + } + } 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", + "
    ", + ); + write_loading_content(w, "
    "); + + if t.auto { + write_small_section_header( + w, + "synthetic-implementors", + "Auto implementors", + "
    ", + ); + write_loading_content(w, "
    "); + } + } + + write!( + w, + "", + 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 {}: {}", + 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 {}", + 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, "{0}", 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 {name}\ + {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, "
    ");
    +        render_attributes(w, it, true);
    +        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true);
    +        write!(w, "
    ") + }); + + 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, + "

    + Fields{}

    ", + 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, + "\ + \ + {name}: {ty}\ + ", + 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, "
    ");
    +        render_attributes(w, it, true);
    +        render_union(w, it, Some(&s.generics), &s.fields, "", true);
    +        write!(w, "
    ") + }); + + 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, + "

    + Fields

    " + ); + for (field, ty) in fields { + let name = field.name.as_ref().expect("union field name"); + let id = format!("{}.{}", ItemType::StructField, name); + write!( + w, + "\ + \ + {name}: {ty}\ + ", + id = id, + name = name, + shortty = ItemType::StructField, + ty = ty.print() + ); + if let Some(stability_class) = field.stability_class() { + write!(w, "", 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, "
    ");
    +        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, "
    ") + }); + + document(w, cx, it); + if !e.variants.is_empty() { + write!( + w, + "

    + Variants{}

    \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, + "
    \ + \ + {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, "
    "); + 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, "
    ", id = variant_id); + write!( + w, + "

    Fields of {name}

    ", + 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, + "\ + \ + {f}: {t}\ + ", + id = id, + f = field.name.as_ref().unwrap(), + t = ty.print() + ); + document(w, cx, field); + } + } + write!(w, "
    "); + } + 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, + "{}", + 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), +} + +impl<'a> AssocItemLink<'a> { + fn anchor(&self, id: &'a String) -> Self { + match *self { + AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(&id)), + ref other => *other, + } + } +} + +enum AssocItemRender<'a> { + All, + DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type, deref_mut_: bool }, +} + +#[derive(Copy, Clone, PartialEq)] +enum RenderMode { + Normal, + ForDeref { mut_: bool }, +} + +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, + "\ +

    \ + Implementations\ +

    \ + " + ); + RenderMode::Normal + } + AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => { + write!( + w, + "\ +

    \ + Methods from {}<Target = {}>\ + \ +

    \ + ", + 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, + "\ +

    \ + Trait Implementations\ +

    \ +
    {}
    ", + impls + ); + } + + if !synthetic.is_empty() { + write!( + w, + "\ +

    \ + Auto Trait Implementations\ + \ +

    \ +
    \ + " + ); + render_impls(cx, w, &synthetic, containing_item, cache); + write!(w, "
    "); + } + + if !blanket_impl.is_empty() { + write!( + w, + "\ +

    \ + Blanket Implementations\ + \ +

    \ +
    \ + " + ); + render_impls(cx, w, &blanket_impl, containing_item, cache); + write!(w, "
    "); + } + } +} + +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!( + "

    Important traits for {}

    \ + ", + impl_.for_.print() + )); + trait_.push_str(&impl_.for_.print().to_string()); + } + + //use the "where" class here to make it small + out.push_str(&format!( + "{}", + 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(" "); + assoc_type( + &mut out, + it, + &[], + Some(&tydef.type_), + AssocItemLink::GotoSource(t_did, &FxHashSet::default()), + "", + ); + out.push_str(";"); + } + } + } + } + } + } + + if !out.is_empty() { + out.insert_str( + 0, + "ⓘ
    " + + ); + out.push_str("
    "); + } + + 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, + 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, "

    ", 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, " "); + assoc_type(w, it, &[], Some(&tydef.type_), AssocItemLink::Anchor(None), ""); + write!(w, ";"); + } + } + } + write!(w, ""); + } else { + write!( + w, + "

    {}", + id, + aliases, + i.inner_impl().print() + ); + } + write!(w, "", 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, "[src]", l, "goto source code"); + } + write!(w, "

    "); + if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) { + let mut ids = cx.id_map.borrow_mut(); + write!( + w, + "
    {}
    ", + 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, "

    ", id, item_type, extra_class); + write!(w, ""); + render_assoc_item(w, item, link.anchor(&id), ItemType::Impl); + write!(w, ""); + render_stability_since_raw(w, item.stable_since(), outer_version); + if let Some(l) = cx.src_href(item, cache) { + write!( + w, + "[src]", + l, "goto source code" + ); + } + write!(w, "

    "); + } + } + clean::TypedefItem(ref tydef, _) => { + let id = cx.derive_id(format!("{}.{}", ItemType::AssocType, name)); + write!(w, "

    ", id, item_type, extra_class); + assoc_type(w, item, &Vec::new(), Some(&tydef.type_), link.anchor(&id), ""); + write!(w, "

    "); + } + clean::AssocConstItem(ref ty, ref default) => { + let id = cx.derive_id(format!("{}.{}", item_type, name)); + write!(w, "

    ", id, item_type, extra_class); + assoc_const(w, item, ty, default.as_ref(), link.anchor(&id), ""); + write!(w, ""); + render_stability_since_raw(w, item.stable_since(), outer_version); + if let Some(l) = cx.src_href(item, cache) { + write!( + w, + "[src]", + l, "goto source code" + ); + } + write!(w, "

    "); + } + clean::AssocTypeItem(ref bounds, ref default) => { + let id = cx.derive_id(format!("{}.{}", item_type, name)); + write!(w, "

    ", id, item_type, extra_class); + assoc_type(w, item, bounds, default.as_ref(), link.anchor(&id), ""); + write!(w, "

    "); + } + 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, "
    "); + 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, "
    "); +} + +fn item_opaque_ty( + w: &mut Buffer, + cx: &Context, + it: &clean::Item, + t: &clean::OpaqueTy, + cache: &Cache, +) { + write!(w, "
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "type {}{}{where_clause} = impl {bounds};
    ", + 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, "
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "trait {}{}{} = {};
    ", + 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, "
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "type {}{}{where_clause} = {type_};
    ", + 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, "
    extern {{");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "    {}type {};\n}}
    ", + 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, + "

    {}{}

    ", + 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, + "
    \ +

    Version {}

    \ +
    ", + Escape(version) + ); + } + } + + write!(buffer, "
    "); + if it.is_crate() { + write!( + buffer, + "

    See all {}'s items

    ", + 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, "

    "); + for (i, name) in cx.current.iter().take(parentlen).enumerate() { + if i > 0 { + write!(buffer, "::"); + } + write!( + buffer, + "{}", + &cx.root_path()[..(cx.current.len() - i - 1) * 3], + *name + ); + } + write!(buffer, "

    "); + + // Sidebar refers to the enclosing module, not this module. + let relpath = if it.is_mod() { "../" } else { "" }; + write!( + buffer, + "", + 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, "", path = relpath); + } + // Closes sidebar-elems div. + write!(buffer, "
    "); +} + +fn get_next_url(used_links: &mut FxHashSet, 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, + deref_mut: bool, +) -> Vec { + 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!( + "{}", + get_next_url(used_links, format!("method.{}", name)), + name + )) + } else { + None + } + } + _ => None, + }) + .collect::>() +} + +// 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::>(); + if !ret.is_empty() { + // We want links' order to be reproducible so we don't use unstable sort. + ret.sort(); + out.push_str(&format!( + "Methods\ +
    {}
    ", + 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(""); + 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(""); + 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::>(); + // We want links' order to be reproducible so we don't use unstable sort. + ret.sort(); + if !ret.is_empty() { + out.push_str(&format!( + "
    {}
    ", + 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!( + "{}{}", + encoded, + if is_negative_impl { "!" } else { "" }, + out + ); + if links.insert(generated.clone()) { Some(generated) } else { None } + } else { + None + } + }) + .collect::>(); + ret.sort(); + ret.join("") + }; + + let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = + v.iter().partition::, _>(|i| i.inner_impl().synthetic); + let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = concrete + .into_iter() + .partition::, _>(|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( + "\ + Trait Implementations", + ); + out.push_str(&format!("
    {}
    ", concrete_format)); + } + + if !synthetic_format.is_empty() { + out.push_str( + "\ + Auto Trait Implementations", + ); + out.push_str(&format!("
    {}
    ", synthetic_format)); + } + + if !blanket_format.is_empty() { + out.push_str( + "\ + Blanket Implementations", + ); + out.push_str(&format!("
    {}
    ", 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!( + "Fields\ +
    {}
    ", + fields + )); + } + } + + sidebar.push_str(&sidebar_assoc_items(it)); + + if !sidebar.is_empty() { + write!(buf, "
    {}
    ", 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!("{name}", name = name)) + } + _ => None, + }) + .collect::>(); + let mut consts = t + .items + .iter() + .filter_map(|m| match m.name { + Some(ref name) if m.is_associated_const() => { + Some(format!("{name}", name = name)) + } + _ => None, + }) + .collect::>(); + let mut required = t + .items + .iter() + .filter_map(|m| match m.name { + Some(ref name) if m.is_ty_method() => { + Some(format!("{name}", name = name)) + } + _ => None, + }) + .collect::>(); + let mut provided = t + .items + .iter() + .filter_map(|m| match m.name { + Some(ref name) if m.is_method() => { + Some(format!("{0}", name)) + } + _ => None, + }) + .collect::>(); + + if !types.is_empty() { + types.sort(); + sidebar.push_str(&format!( + "\ + Associated Types
    {}
    ", + types.join("") + )); + } + if !consts.is_empty() { + consts.sort(); + sidebar.push_str(&format!( + "\ + Associated Constants
    {}
    ", + consts.join("") + )); + } + if !required.is_empty() { + required.sort(); + sidebar.push_str(&format!( + "\ + Required Methods
    {}
    ", + required.join("") + )); + } + if !provided.is_empty() { + provided.sort(); + sidebar.push_str(&format!( + "\ + Provided Methods
    {}
    ", + 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::>(); + + if !res.is_empty() { + res.sort(); + sidebar.push_str(&format!( + "\ + Implementations on Foreign Types
    {}
    ", + res.into_iter() + .map(|(name, id)| format!("{}", id, Escape(&name))) + .collect::>() + .join("") + )); + } + } + + sidebar.push_str(&sidebar_assoc_items(it)); + + sidebar.push_str("Implementors"); + if t.auto { + sidebar.push_str( + "Auto Implementors", + ); + } + + write!(buf, "
    {}
    ", sidebar) +} + +fn sidebar_primitive(buf: &mut Buffer, it: &clean::Item) { + let sidebar = sidebar_assoc_items(it); + + if !sidebar.is_empty() { + write!(buf, "
    {}
    ", sidebar); + } +} + +fn sidebar_typedef(buf: &mut Buffer, it: &clean::Item) { + let sidebar = sidebar_assoc_items(it); + + if !sidebar.is_empty() { + write!(buf, "
    {}
    ", 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!("{name}", name = name)) + } + _ => None, + }) + .collect::>(); + 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!( + "Fields\ +
    {}
    ", + fields + )); + } + + sidebar.push_str(&sidebar_assoc_items(it)); + + if !sidebar.is_empty() { + write!(buf, "
    {}
    ", 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!("{name}", name = name)), + _ => None, + }) + .collect::>(); + if !variants.is_empty() { + variants.sort_unstable(); + sidebar.push_str(&format!( + "Variants\ +
    {}
    ", + variants.join(""), + )); + } + + sidebar.push_str(&sidebar_assoc_items(it)); + + if !sidebar.is_empty() { + write!(buf, "
    {}
    ", 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!( + "
  • {name}
  • ", + 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!( + "
  • {name}
  • ", + id = short, + name = name + )); + } + } + + if !sidebar.is_empty() { + write!(buf, "
      {}
    ", sidebar); + } +} + +fn sidebar_foreign_type(buf: &mut Buffer, it: &clean::Item) { + let sidebar = sidebar_assoc_items(it); + if !sidebar.is_empty() { + write!(buf, "
    {}
    ", 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, "
    ");
    +            write!(w, "{}!() {{ /* proc-macro */ }}", name);
    +            write!(w, "
    "); + } + MacroKind::Attr => { + write!(w, "
    ");
    +            write!(w, "#[{}]", name);
    +            write!(w, "
    "); + } + MacroKind::Derive => { + write!(w, "
    ");
    +            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, "
    "); + } + } + 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 { + 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 +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 715956ea172..0de9c6336bb 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -66,19 +66,8 @@ mod doctree; #[macro_use] mod error; mod fold; -mod formats; -pub mod html { - crate mod escape; - crate mod format; - crate mod highlight; - crate mod item_type; - crate mod layout; - pub mod markdown; - crate mod render; - crate mod sources; - crate mod static_files; - crate mod toc; -} +crate mod formats; +crate mod html; mod markdown; mod passes; mod test; -- cgit 1.4.1-3-g733a5 From a7909522547cb35b32a4f11b78b2b54864189295 Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Fri, 26 Jun 2020 08:18:20 -0500 Subject: Pull out more types from html --- src/librustdoc/formats/mod.rs | 11 +++++++++++ src/librustdoc/formats/renderer.rs | 4 +++- src/librustdoc/html/render/mod.rs | 15 ++------------- 3 files changed, 16 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index 2473f7758d2..97e1af13b8a 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -9,6 +9,17 @@ use rustc_span::def_id::DefId; use crate::clean; use crate::clean::types::GetDefId; +pub enum AssocItemRender<'a> { + All, + DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type, deref_mut_: bool }, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum RenderMode { + Normal, + ForDeref { mut_: bool }, +} + /// Metadata about implementations for a type or trait. #[derive(Clone, Debug)] pub struct Impl { diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index f1862337ba1..d4ba6726cd2 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -10,6 +10,8 @@ use crate::formats::cache::{Cache, CACHE_KEY}; pub trait FormatRenderer: Clone { type Output: FormatRenderer; + /// Sets up any state required for the emulator. When this is called the cache has already been + /// populated. fn init( krate: clean::Crate, options: RenderOptions, @@ -30,7 +32,7 @@ pub trait FormatRenderer: Clone { ) -> Result<(), Error>; /// Runs after recursively rendering all sub-items of a module. - fn mod_item_out(&mut self, name: &str) -> Result<(), Error>; + fn mod_item_out(&mut self, item_name: &str) -> Result<(), Error>; /// Post processing hook for cleanup and dumping output to files. fn after_krate(&mut self, krate: &clean::Crate, cache: &Cache) -> Result<(), Error>; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 1678cff16a9..7140cf00b6e 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -70,7 +70,7 @@ use crate::doctree; use crate::error::Error; use crate::formats::cache::{cache, Cache}; use crate::formats::item_type::ItemType; -use crate::formats::{FormatRenderer, Impl}; +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; @@ -626,7 +626,7 @@ impl FormatRenderer for Context { Ok(()) } - fn mod_item_out(&mut self, _name: &str) -> Result<(), Error> { + fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> { info!("Recursed; leaving {}", self.dst.display()); // Go back to where we were at @@ -3255,17 +3255,6 @@ impl<'a> AssocItemLink<'a> { } } -enum AssocItemRender<'a> { - All, - DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type, deref_mut_: bool }, -} - -#[derive(Copy, Clone, PartialEq)] -enum RenderMode { - Normal, - ForDeref { mut_: bool }, -} - fn render_assoc_items( w: &mut Buffer, cx: &Context, -- cgit 1.4.1-3-g733a5 From 65bf5d5248635152262344770591c367ba6a9890 Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Fri, 26 Jun 2020 09:14:45 -0500 Subject: TODO -> FIXME --- src/librustdoc/formats/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 4bdeafa1ec7..1ca462b181d 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -174,7 +174,7 @@ impl Cache { }; // Cache where all our extern crates are located - // TODO: this part is specific to HTML so it'd be nice to remove it from the common code + // FIXME: this part is specific to HTML so it'd be nice to remove it from the common code for &(n, ref e) in &krate.externs { let src_root = match e.src { FileName::Real(ref p) => match p.local_path().parent() { -- cgit 1.4.1-3-g733a5 From 3d707a008e0822471de4adad047b5cefd281f3ac Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Mon, 29 Jun 2020 18:22:58 -0500 Subject: Make requested changes --- src/librustdoc/formats/cache.rs | 4 ++-- src/librustdoc/formats/mod.rs | 4 ++++ src/librustdoc/formats/renderer.rs | 28 +++++++++++++++------------- src/librustdoc/html/render/mod.rs | 6 ++---- src/librustdoc/lib.rs | 2 +- 5 files changed, 24 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 1ca462b181d..72881eccf3e 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -126,7 +126,7 @@ pub struct Cache { impl Cache { pub fn from_krate( - renderinfo: RenderInfo, + render_info: RenderInfo, document_private: bool, extern_html_root_urls: &BTreeMap, dst: &Path, @@ -142,7 +142,7 @@ impl Cache { deref_mut_trait_did, owned_box_did, .. - } = renderinfo; + } = render_info; let external_paths = external_paths.into_iter().map(|(k, (v, t))| (k, (v, ItemType::from(t)))).collect(); diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index 97e1af13b8a..7757ee7e515 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -9,11 +9,15 @@ use rustc_span::def_id::DefId; use crate::clean; use crate::clean::types::GetDefId; +/// Specifies whether rendering directly implemented trait items or ones from a certain Deref +/// impl. pub enum AssocItemRender<'a> { All, DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type, deref_mut_: bool }, } +/// For different handling of associated items from the Deref target of a type rather than the type +/// itself. #[derive(Copy, Clone, PartialEq)] pub enum RenderMode { Normal, diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index d4ba6726cd2..7d23c7b8aff 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -7,23 +7,24 @@ use crate::config::{RenderInfo, RenderOptions}; use crate::error::Error; use crate::formats::cache::{Cache, CACHE_KEY}; +/// Allows for different backends to rustdoc to be used with the `Renderer::run()` function. Each +/// backend renderer has hooks for initialization, documenting an item, entering and exiting a +/// module, and cleanup/finalizing output. pub trait FormatRenderer: Clone { - type Output: FormatRenderer; - - /// Sets up any state required for the emulator. When this is called the cache has already been + /// Sets up any state required for the renderer. When this is called the cache has already been /// populated. fn init( krate: clean::Crate, options: RenderOptions, - renderinfo: RenderInfo, + render_info: RenderInfo, edition: Edition, cache: &mut Cache, - ) -> Result<(Self::Output, clean::Crate), Error>; + ) -> Result<(Self, clean::Crate), Error>; /// Renders a single non-module item. This means no recursive sub-item rendering is required. fn item(&mut self, item: clean::Item, cache: &Cache) -> Result<(), Error>; - /// Renders a module (doesn't need to handle recursing into children). + /// Renders a module (should not handle recursing into children). fn mod_item_in( &mut self, item: &clean::Item, @@ -54,19 +55,20 @@ impl Renderer { self, krate: clean::Crate, options: RenderOptions, - renderinfo: RenderInfo, + render_info: RenderInfo, diag: &rustc_errors::Handler, edition: Edition, ) -> Result<(), Error> { let (krate, mut cache) = Cache::from_krate( - renderinfo.clone(), + render_info.clone(), options.document_private, &options.extern_html_root_urls, &options.output, krate, ); - let (mut renderer, mut krate) = T::init(krate, options, renderinfo, edition, &mut cache)?; + let (mut format_renderer, mut krate) = + T::init(krate, options, render_info, edition, &mut cache)?; let cache = Arc::new(cache); // Freeze the cache now that the index has been built. Put an Arc into TLS for future @@ -81,7 +83,7 @@ impl Renderer { item.name = Some(krate.name.clone()); // Render the crate documentation - let mut work = vec![(renderer.clone(), item)]; + let mut work = vec![(format_renderer.clone(), item)]; while let Some((mut cx, item)) = work.pop() { if item.is_mod() { @@ -98,7 +100,7 @@ impl Renderer { _ => unreachable!(), }; for it in module.items { - info!("Adding {:?} to worklist", it.name); + debug!("Adding {:?} to worklist", it.name); work.push((cx.clone(), it)); } @@ -108,7 +110,7 @@ impl Renderer { } } - renderer.after_krate(&krate, &cache)?; - renderer.after_run(diag) + format_renderer.after_krate(&krate, &cache)?; + format_renderer.after_run(diag) } } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 7140cf00b6e..46c1b27986d 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -371,14 +371,12 @@ pub fn initial_ids() -> Vec { .collect() } +/// Generates the documentation for `crate` into the directory `dst` impl FormatRenderer for Context { - type Output = Self; - - /// Generates the documentation for `crate` into the directory `dst` fn init( mut krate: clean::Crate, options: RenderOptions, - _renderinfo: RenderInfo, + _render_info: RenderInfo, edition: Edition, cache: &mut Cache, ) -> Result<(Context, clean::Crate), Error> { diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 0de9c6336bb..04651da4d09 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -67,7 +67,7 @@ mod doctree; mod error; mod fold; crate mod formats; -crate mod html; +pub mod html; mod markdown; mod passes; mod test; -- cgit 1.4.1-3-g733a5 From cee8023c690158daf4f6c3d8bf2d32297fdfed0c Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Mon, 27 Jul 2020 17:34:17 -0500 Subject: More requested changes --- src/librustdoc/core.rs | 4 +- src/librustdoc/formats/cache.rs | 14 +---- src/librustdoc/formats/mod.rs | 2 +- src/librustdoc/formats/renderer.rs | 126 +++++++++++++++++-------------------- src/librustdoc/html/render/mod.rs | 6 +- src/librustdoc/lib.rs | 6 +- 6 files changed, 69 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 7635453d56f..2c2ebc9291b 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -44,9 +44,9 @@ pub type ExternalPaths = FxHashMap, clean::TypeKind)>; pub struct DocContext<'tcx> { pub tcx: TyCtxt<'tcx>, pub resolver: Rc>, - /// Later on moved into `formats::cache::CACHE_KEY` + /// Later on moved into `CACHE_KEY` pub renderinfo: RefCell, - /// Later on moved through `clean::Crate` into `formats::cache::CACHE_KEY` + /// Later on moved through `clean::Crate` into `CACHE_KEY` pub external_traits: Rc>>, /// Used while populating `external_traits` to ensure we don't process the same trait twice at /// the same time. diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 72881eccf3e..99b31473f87 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -148,29 +148,19 @@ impl Cache { 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::default() }; // Cache where all our extern crates are located @@ -211,7 +201,7 @@ impl Cache { 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()); + cache.impls.entry(did).or_default().push(impl_.clone()); } } } diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index 7757ee7e515..dcb0184c58c 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -2,7 +2,7 @@ pub mod cache; pub mod item_type; pub mod renderer; -pub use renderer::{FormatRenderer, Renderer}; +pub use renderer::{run_format, FormatRenderer}; use rustc_span::def_id::DefId; diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index 7d23c7b8aff..90ace4d44c4 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -7,7 +7,7 @@ use crate::config::{RenderInfo, RenderOptions}; use crate::error::Error; use crate::formats::cache::{Cache, CACHE_KEY}; -/// Allows for different backends to rustdoc to be used with the `Renderer::run()` function. Each +/// Allows for different backends to rustdoc to be used with the `run_format()` function. Each /// backend renderer has hooks for initialization, documenting an item, entering and exiting a /// module, and cleanup/finalizing output. pub trait FormatRenderer: Clone { @@ -42,75 +42,65 @@ pub trait FormatRenderer: Clone { fn after_run(&mut self, diag: &rustc_errors::Handler) -> Result<(), Error>; } -#[derive(Clone)] -pub struct Renderer; - -impl Renderer { - pub fn new() -> Renderer { - Renderer - } +/// Main method for rendering a crate. +pub fn run_format( + krate: clean::Crate, + options: RenderOptions, + render_info: RenderInfo, + diag: &rustc_errors::Handler, + edition: Edition, +) -> Result<(), Error> { + let (krate, mut cache) = Cache::from_krate( + render_info.clone(), + options.document_private, + &options.extern_html_root_urls, + &options.output, + krate, + ); + + let (mut format_renderer, mut krate) = + T::init(krate, options, render_info, edition, &mut cache)?; + + let cache = Arc::new(cache); + // Freeze the cache now that the index has been built. Put an Arc into TLS for future + // parallelization opportunities + CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); + + let mut item = match krate.module.take() { + Some(i) => i, + None => return Ok(()), + }; + + item.name = Some(krate.name.clone()); + + // Render the crate documentation + let mut work = vec![(format_renderer.clone(), item)]; + + while let Some((mut cx, item)) = work.pop() { + if item.is_mod() { + // modules are special because they add a namespace. We also need to + // recurse into the items of the module as well. + let name = item.name.as_ref().unwrap().to_string(); + if name.is_empty() { + panic!("Unexpected module with empty name"); + } - /// Main method for rendering a crate. - pub fn run( - self, - krate: clean::Crate, - options: RenderOptions, - render_info: RenderInfo, - diag: &rustc_errors::Handler, - edition: Edition, - ) -> Result<(), Error> { - let (krate, mut cache) = Cache::from_krate( - render_info.clone(), - options.document_private, - &options.extern_html_root_urls, - &options.output, - krate, - ); - - let (mut format_renderer, mut krate) = - T::init(krate, options, render_info, edition, &mut cache)?; - - let cache = Arc::new(cache); - // Freeze the cache now that the index has been built. Put an Arc into TLS for future - // parallelization opportunities - CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); - - let mut item = match krate.module.take() { - Some(i) => i, - None => return Ok(()), - }; - - item.name = Some(krate.name.clone()); - - // Render the crate documentation - let mut work = vec![(format_renderer.clone(), item)]; - - while let Some((mut cx, item)) = work.pop() { - if item.is_mod() { - // modules are special because they add a namespace. We also need to - // recurse into the items of the module as well. - let name = item.name.as_ref().unwrap().to_string(); - if name.is_empty() { - panic!("Unexpected module with empty name"); - } - - cx.mod_item_in(&item, &name, &cache)?; - let module = match item.inner { - clean::StrippedItem(box clean::ModuleItem(m)) | clean::ModuleItem(m) => m, - _ => unreachable!(), - }; - for it in module.items { - debug!("Adding {:?} to worklist", it.name); - work.push((cx.clone(), it)); - } - - cx.mod_item_out(&name)?; - } else if item.name.is_some() { - cx.item(item, &cache)?; + cx.mod_item_in(&item, &name, &cache)?; + let module = match item.inner { + clean::StrippedItem(box clean::ModuleItem(m)) | clean::ModuleItem(m) => m, + _ => unreachable!(), + }; + for it in module.items { + debug!("Adding {:?} to worklist", it.name); + work.push((cx.clone(), it)); } - } - format_renderer.after_krate(&krate, &cache)?; - format_renderer.after_run(diag) + cx.mod_item_out(&name)?; + } else if item.name.is_some() { + cx.item(item, &cache)?; + } } + + format_renderer.after_krate(&krate, &cache)?; + format_renderer.after_run(diag) } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 46c1b27986d..a6edb8ace33 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -228,7 +228,7 @@ impl Serialize for IndexItem { /// A type used for the search index. #[derive(Debug)] -pub struct RenderType { +crate struct RenderType { ty: Option, idx: Option, name: Option, @@ -259,7 +259,7 @@ impl Serialize for RenderType { /// A type used for the search index. #[derive(Debug)] -pub struct Generic { +crate struct Generic { name: String, defid: Option, idx: Option, @@ -313,7 +313,7 @@ impl Serialize for IndexItemFunctionType { } #[derive(Debug)] -pub struct TypeWithKind { +crate struct TypeWithKind { ty: RenderType, kind: TypeKind, } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 04651da4d09..a4ce84556f3 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -502,9 +502,9 @@ fn main_options(options: config::Options) -> i32 { info!("going to format"); let (error_format, edition, debugging_options) = diag_opts; let diag = core::new_handler(error_format, None, &debugging_options); - match formats::Renderer::new() - .run::(krate, renderopts, renderinfo, &diag, edition) - { + match formats::run_format::( + krate, renderopts, renderinfo, &diag, edition, + ) { Ok(_) => rustc_driver::EXIT_SUCCESS, Err(e) => { diag.struct_err(&format!("couldn't generate documentation: {}", e.error)) -- cgit 1.4.1-3-g733a5 From 7621a5b635542c58b6576dd1ae8396df6d8d1bf0 Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Wed, 29 Jul 2020 16:15:31 -0500 Subject: Refactor DocFS to fix error handling bugs --- src/librustdoc/docfs.rs | 61 ++++++++++----------------------------- src/librustdoc/html/render/mod.rs | 17 ++++++----- src/librustdoc/lib.rs | 11 +++++-- 3 files changed, 34 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/librustdoc/docfs.rs b/src/librustdoc/docfs.rs index 9a11e8fce28..119059aa4a0 100644 --- a/src/librustdoc/docfs.rs +++ b/src/librustdoc/docfs.rs @@ -13,8 +13,7 @@ use std::fs; use std::io; use std::path::Path; use std::string::ToString; -use std::sync::mpsc::{channel, Receiver, Sender}; -use std::sync::Arc; +use std::sync::mpsc::Sender; macro_rules! try_err { ($e:expr, $file:expr) => { @@ -31,47 +30,24 @@ pub trait PathError { S: ToString + Sized; } -pub struct ErrorStorage { - sender: Option>>, - receiver: Receiver>, -} - -impl ErrorStorage { - pub fn new() -> ErrorStorage { - let (sender, receiver) = channel(); - ErrorStorage { sender: Some(sender), receiver } - } - - /// Prints all stored errors. Returns the number of printed errors. - pub fn write_errors(&mut self, diag: &rustc_errors::Handler) -> usize { - let mut printed = 0; - // In order to drop the sender part of the channel. - self.sender = None; - - for msg in self.receiver.iter() { - if let Some(ref error) = msg { - diag.struct_err(&error).emit(); - printed += 1; - } - } - printed - } -} - pub struct DocFS { sync_only: bool, - errors: Arc, + errors: Option>, } impl DocFS { - pub fn new(errors: &Arc) -> DocFS { - DocFS { sync_only: false, errors: Arc::clone(errors) } + pub fn new(errors: &Sender) -> DocFS { + DocFS { sync_only: false, errors: Some(errors.clone()) } } pub fn set_sync_only(&mut self, sync_only: bool) { self.sync_only = sync_only; } + pub fn close(&mut self) { + self.errors = None; + } + pub fn create_dir_all>(&self, path: P) -> io::Result<()> { // For now, dir creation isn't a huge time consideration, do it // synchronously, which avoids needing ordering between write() actions @@ -88,20 +64,15 @@ impl DocFS { if !self.sync_only && cfg!(windows) { // A possible future enhancement after more detailed profiling would // be to create the file sync so errors are reported eagerly. - let contents = contents.as_ref().to_vec(); let path = path.as_ref().to_path_buf(); - let sender = self.errors.sender.clone().unwrap(); - rayon::spawn(move || match fs::write(&path, &contents) { - Ok(_) => { - sender.send(None).unwrap_or_else(|_| { - panic!("failed to send error on \"{}\"", path.display()) - }); - } - Err(e) => { - sender.send(Some(format!("\"{}\": {}", path.display(), e))).unwrap_or_else( - |_| panic!("failed to send non-error on \"{}\"", path.display()), - ); - } + let contents = contents.as_ref().to_vec(); + let sender = self.errors.clone().expect("can't write after closing"); + rayon::spawn(move || { + fs::write(&path, contents).unwrap_or_else(|e| { + sender + .send(format!("\"{}\": {}", path.display(), e)) + .expect(&format!("failed to send error on \"{}\"", path.display())); + }); }); Ok(()) } else { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index a6edb8ace33..715b5e64764 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -44,6 +44,7 @@ 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; @@ -65,7 +66,7 @@ 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, ErrorStorage, PathError}; +use crate::docfs::{DocFS, PathError}; use crate::doctree; use crate::error::Error; use crate::formats::cache::{cache, Cache}; @@ -113,7 +114,9 @@ crate struct Context { id_map: Rc>, pub shared: Arc, all: Rc>, - pub errors: Arc, + /// Storage for the errors produced while generating documentation so they + /// can be printed together at the end. + pub errors: Rc>, } crate struct SharedContext { @@ -403,7 +406,6 @@ impl FormatRenderer for Context { }, _ => PathBuf::new(), }; - let errors = Arc::new(ErrorStorage::new()); // If user passed in `--playground-url` arg, we fill in crate name here let mut playground = None; if let Some(url) = playground_url { @@ -447,6 +449,7 @@ impl FormatRenderer for Context { } } } + let (sender, receiver) = channel(); let mut scx = SharedContext { collapsed: krate.collapsed, src_root, @@ -459,7 +462,7 @@ impl FormatRenderer for Context { style_files, resource_suffix, static_root_path, - fs: DocFS::new(&errors), + fs: DocFS::new(&sender), edition, codes: ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build()), playground, @@ -493,7 +496,7 @@ impl FormatRenderer for Context { id_map: Rc::new(RefCell::new(id_map)), shared: Arc::new(scx), all: Rc::new(RefCell::new(AllTypes::new())), - errors, + errors: Rc::new(receiver), }; CURRENT_DEPTH.with(|s| s.set(0)); @@ -506,8 +509,8 @@ impl FormatRenderer for Context { } fn after_run(&mut self, diag: &rustc_errors::Handler) -> Result<(), Error> { - let nb_errors = - Arc::get_mut(&mut self.errors).map_or_else(|| 0, |errors| errors.write_errors(diag)); + 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 { diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index a4ce84556f3..65bc089faf4 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -507,9 +507,14 @@ fn main_options(options: config::Options) -> i32 { ) { Ok(_) => rustc_driver::EXIT_SUCCESS, Err(e) => { - diag.struct_err(&format!("couldn't generate documentation: {}", e.error)) - .note(&format!("failed to create or modify \"{}\"", e.file.display())) - .emit(); + let mut msg = + diag.struct_err(&format!("couldn't generate documentation: {}", e.error)); + let file = e.file.display().to_string(); + if file.is_empty() { + msg.emit() + } else { + msg.note(&format!("failed to create or modify \"{}\"", file)).emit() + } rustc_driver::EXIT_FAILURE } } -- cgit 1.4.1-3-g733a5 From 29df0508f3106d152ad2cd1b41cf627b98ea9d6f Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Wed, 29 Jul 2020 16:48:22 -0500 Subject: Pass by value --- src/librustdoc/docfs.rs | 4 ++-- src/librustdoc/html/render/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/librustdoc/docfs.rs b/src/librustdoc/docfs.rs index 119059aa4a0..4ce6bcbe274 100644 --- a/src/librustdoc/docfs.rs +++ b/src/librustdoc/docfs.rs @@ -36,8 +36,8 @@ pub struct DocFS { } impl DocFS { - pub fn new(errors: &Sender) -> DocFS { - DocFS { sync_only: false, errors: Some(errors.clone()) } + pub fn new(errors: Sender) -> DocFS { + DocFS { sync_only: false, errors: Some(errors) } } pub fn set_sync_only(&mut self, sync_only: bool) { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 715b5e64764..5fb2d9f6f91 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -462,7 +462,7 @@ impl FormatRenderer for Context { style_files, resource_suffix, static_root_path, - fs: DocFS::new(&sender), + fs: DocFS::new(sender), edition, codes: ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build()), playground, -- cgit 1.4.1-3-g733a5