diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs new file mode 100644 index 0000000000000..976168a9ac407 --- /dev/null +++ b/src/librustdoc/html/render/context.rs @@ -0,0 +1,617 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::mpsc::{channel, Receiver}; +use std::sync::Arc; + +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_middle::ty::TyCtxt; +use rustc_session::Session; +use rustc_span::edition::Edition; +use rustc_span::source_map::FileName; +use rustc_span::symbol::sym; + +use super::cache::{build_index, ExternalLocation}; +use super::print_item::{full_path, item_path, print_item}; +use super::write_shared::write_shared; +use super::{ + print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS, + CURRENT_DEPTH, INITIAL_IDS, +}; + +use crate::clean::{self, AttributesExt}; +use crate::config::RenderOptions; +use crate::docfs::{DocFS, PathError}; +use crate::error::Error; +use crate::formats::cache::Cache; +use crate::formats::item_type::ItemType; +use crate::formats::FormatRenderer; +use crate::html::escape::Escape; +use crate::html::format::Buffer; +use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; +use crate::html::{layout, sources}; + +/// 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<'tcx> { + /// Current hierarchy of components leading down to what's currently being + /// rendered + crate current: Vec, + /// The current destination folder of where HTML artifacts should be placed. + /// This changes as the context descends into the module hierarchy. + crate 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. + crate render_redirect_pages: bool, + /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set + /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of + /// the crate. + crate redirections: Option>>>, + /// The map used to ensure all generated 'id=' attributes are unique. + pub(super) id_map: Rc>, + /// Tracks section IDs for `Deref` targets so they match in both the main + /// body and the sidebar. + pub(super) deref_id_map: Rc>>, + crate shared: Arc>, + all: Rc>, + /// Storage for the errors produced while generating documentation so they + /// can be printed together at the end. + crate errors: Rc>, + crate cache: Rc, +} + +impl<'tcx> Context<'tcx> { + pub(super) 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 (base, ext) = filename.split_once('.').unwrap(); + let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext); + self.dst.join(&filename) + } + + pub(super) fn tcx(&self) -> TyCtxt<'tcx> { + self.shared.tcx + } + + fn sess(&self) -> &'tcx Session { + &self.shared.tcx.sess + } + + pub(super) 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. + pub(super) 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.unwrap().as_str()); + } + title.push_str(" - Rust"); + let tyname = it.type_(); + let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc)); + let desc = if let Some(desc) = desc { + desc + } else 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 { + if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) { + let mut path = String::new(); + for name in &names[..names.len() - 1] { + path.push_str(name); + path.push('/'); + } + path.push_str(&item_path(ty, names.last().unwrap())); + match self.redirections { + Some(ref redirections) => { + let mut current_path = String::new(); + for name in &self.current { + current_path.push_str(name); + current_path.push('/'); + } + current_path.push_str(&item_path(ty, names.last().unwrap())); + redirections.borrow_mut().insert(current_path, path); + } + None => return layout::redirect(&format!("{}{}", self.root_path(), path)), + } + } + String::new() + } + } + + /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. + 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(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))), + )); + } + + 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. + pub(super) fn src_href(&self, item: &clean::Item) -> Option { + if item.source.is_dummy() { + return None; + } + let mut root = self.root_path(); + let mut path = String::new(); + let cnum = item.source.cnum(self.sess()); + + // We can safely ignore synthetic `SourceFile`s. + let file = match item.source.filename(self.sess()) { + FileName::Real(ref path) => path.local_path().to_path_buf(), + _ => return None, + }; + let file = &file; + + let symbol; + let (krate, path) = if cnum == LOCAL_CRATE { + if let Some(path) = self.shared.local_sources.get(file) { + (self.shared.layout.krate.as_str(), path) + } else { + return None; + } + } else { + let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? { + (name, ref src, ExternalLocation::Local) => (name, src), + (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()); + symbol = krate.as_str(); + (&*symbol, &path) + }; + + let loline = item.source.lo(self.sess()).line; + let hiline = item.source.hi(self.sess()).line; + let lines = + if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) }; + Some(format!( + "{root}src/{krate}/{path}#{lines}", + root = Escape(&root), + krate = krate, + path = path, + lines = lines + )) + } +} + +/// Generates the documentation for `crate` into the directory `dst` +impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { + fn descr() -> &'static str { + "html" + } + + fn init( + mut krate: clean::Crate, + options: RenderOptions, + edition: Edition, + mut cache: Cache, + tcx: TyCtxt<'tcx>, + ) -> Result<(Self, 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, + default_settings, + extension_css, + resource_suffix, + static_root_path, + generate_search_filter, + unstable_features, + generate_redirect_map, + .. + } = options; + + let src_root = match krate.src { + FileName::Real(ref p) => match p.local_path().parent() { + Some(p) => p.to_path_buf(), + None => PathBuf::new(), + }, + _ => PathBuf::new(), + }; + // If user passed in `--playground-url` arg, we fill in crate name here + let mut playground = None; + if let Some(url) = playground_url { + playground = + Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url }); + } + let mut layout = layout::Layout { + logo: String::new(), + favicon: String::new(), + external_html, + default_settings, + krate: krate.name.to_string(), + 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.to_string()), + url: s.to_string(), + }); + } + (sym::issue_tracker_base_url, Some(s)) => { + issue_tracker_base_url = Some(s.to_string()); + } + (sym::html_no_source, None) if attr.is_word() => { + include_sources = false; + } + _ => {} + } + } + } + let (sender, receiver) = channel(); + let mut scx = SharedContext { + tcx, + collapsed: krate.collapsed, + src_root, + include_sources, + local_sources: Default::default(), + issue_tracker_base_url, + layout, + created_dirs: Default::default(), + sort_modules_alphabetically, + style_files, + resource_suffix, + static_root_path, + fs: DocFS::new(sender), + edition, + codes: ErrorCodes::from(unstable_features.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, &mut cache, tcx); + + let mut cx = Context { + current: Vec::new(), + dst, + render_redirect_pages: false, + id_map: Rc::new(RefCell::new(id_map)), + deref_id_map: Rc::new(RefCell::new(FxHashMap::default())), + shared: Arc::new(scx), + all: Rc::new(RefCell::new(AllTypes::new())), + errors: Rc::new(receiver), + cache: Rc::new(cache), + redirections: if generate_redirect_map { Some(Default::default()) } else { None }, + }; + + 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_krate( + &mut self, + krate: &clean::Crate, + diag: &rustc_errors::Handler, + ) -> Result<(), Error> { + let final_file = self.dst.join(&*krate.name.as_str()).join("all.html"); + let settings_file = self.dst.join("settings.html"); + let crate_name = krate.name; + + 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, + &self.shared.style_files, + )?, + &style_files, + ); + self.shared.fs.write(&settings_file, v.as_bytes())?; + if let Some(redirections) = self.redirections.take() { + if !redirections.borrow().is_empty() { + let redirect_map_path = + self.dst.join(&*krate.name.as_str()).join("redirect-map.json"); + let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); + self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?; + self.shared.fs.write(&redirect_map_path, paths.as_bytes())?; + } + } + + // Flush pending errors. + Arc::get_mut(&mut self.shared).unwrap().fs.close(); + let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count(); + if nb_errors > 0 { + Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) + } else { + Ok(()) + } + } + + fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> 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 module = match *item.kind { + clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m, + _ => unreachable!(), + }; + let items = self.build_sidebar_items(module); + let js_dst = self.dst.join("sidebar-items.js"); + let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap()); + scx.fs.write(&js_dst, &v)?; + } + Ok(()) + } + + fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> { + info!("Recursed; leaving {}", self.dst.display()); + + // Go back to where we were at + self.dst.pop(); + self.current.pop(); + Ok(()) + } + + fn item(&mut self, item: clean::Item) -> 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.as_str()); + 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); + if let Some(ref redirections) = self.redirections { + let crate_name = &self.shared.layout.krate; + redirections.borrow_mut().insert( + format!("{}/{}", crate_name, redir_name), + format!("{}/{}", crate_name, file_name), + ); + } else { + let v = layout::redirect(file_name); + let redir_dst = self.dst.join(redir_name); + self.shared.fs.write(&redir_dst, v.as_bytes())?; + } + } + } + Ok(()) + } + + fn cache(&self) -> &Cache { + &self.cache + } +} + +fn make_item_keywords(it: &clean::Item) -> String { + format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap()) +} diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index fbeeb2a6fa2f2..50cae50c2c3eb 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1,5 +1,3 @@ -// ignore-tidy-filelength - //! Rustdoc's HTML rendering module. //! //! This modules contains the bulk of the logic necessary for rendering a @@ -30,58 +28,48 @@ crate mod cache; #[cfg(test)] mod tests; +mod context; +mod print_item; +mod write_shared; + +crate use context::*; + use std::cell::{Cell, RefCell}; -use std::cmp::Ordering; -use std::collections::{BTreeMap, VecDeque}; +use std::collections::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::fmt; +use std::path::{Path, PathBuf}; use std::str; use std::string::ToString; -use std::sync::mpsc::{channel, Receiver}; -use std::sync::Arc; use itertools::Itertools; use rustc_ast_pretty::pprust; use rustc_attr::{Deprecation, StabilityLevel}; -use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::def_id::DefId; use rustc_hir::Mutability; use rustc_middle::middle::stability; use rustc_middle::ty::TyCtxt; -use rustc_session::Session; use rustc_span::edition::Edition; -use rustc_span::hygiene::MacroKind; -use rustc_span::source_map::FileName; use rustc_span::symbol::{kw, sym, Symbol}; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; -use crate::clean::{self, AttributesExt, GetDefId, RenderedLink, SelfTy, TypeKind}; -use crate::config::RenderOptions; +use crate::clean::{self, GetDefId, RenderedLink, SelfTy, TypeKind}; use crate::docfs::{DocFS, PathError}; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode}; use crate::html::escape::Escape; -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, plain_text_summary, ErrorCodes, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine, +use crate::html::format::{ + href, print_abi_with_space, print_default_space, print_generic_bounds, Buffer, Function, + PrintWithSpace, WhereClause, }; -use crate::html::sources; -use crate::html::{highlight, layout, static_files}; -use cache::{build_index, ExternalLocation}; +use crate::html::layout; +use crate::html::markdown::{self, ErrorCodes, Markdown, MarkdownHtml, MarkdownSummaryLine}; /// A pair of name and its optional document. crate type NameDoc = (String, Option); @@ -92,42 +80,6 @@ crate fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { }) } -/// 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<'tcx> { - /// Current hierarchy of components leading down to what's currently being - /// rendered - crate current: Vec, - /// The current destination folder of where HTML artifacts should be placed. - /// This changes as the context descends into the module hierarchy. - crate 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. - crate render_redirect_pages: bool, - /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set - /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of - /// the crate. - crate redirections: Option>>>, - /// The map used to ensure all generated 'id=' attributes are unique. - id_map: Rc>, - /// Tracks section IDs for `Deref` targets so they match in both the main - /// body and the sidebar. - deref_id_map: Rc>>, - crate shared: Arc>, - all: Rc>, - /// Storage for the errors produced while generating documentation so they - /// can be printed together at the end. - crate errors: Rc>, - crate cache: Rc, -} - crate struct SharedContext<'tcx> { crate tcx: TyCtxt<'tcx>, /// The path to the crate root source minus the file name. @@ -169,27 +121,6 @@ crate struct SharedContext<'tcx> { playground: Option, } -impl<'tcx> Context<'tcx> { - 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 (base, ext) = filename.split_once('.').unwrap(); - let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext); - self.dst.join(&filename) - } - - fn tcx(&self) -> TyCtxt<'tcx> { - self.shared.tcx - } - - fn sess(&self) -> &'tcx Session { - &self.shared.tcx.sess - } -} - impl SharedContext<'_> { crate fn ensure_dir(&self, dst: &Path) -> Result<(), Error> { let mut dirs = self.created_dirs.borrow_mut(); @@ -380,860 +311,6 @@ crate const INITIAL_IDS: [&'static str; 15] = [ "implementations", ]; -/// Generates the documentation for `crate` into the directory `dst` -impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { - fn descr() -> &'static str { - "html" - } - - fn init( - mut krate: clean::Crate, - options: RenderOptions, - edition: Edition, - mut cache: Cache, - tcx: TyCtxt<'tcx>, - ) -> Result<(Self, 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, - default_settings, - extension_css, - resource_suffix, - static_root_path, - generate_search_filter, - unstable_features, - generate_redirect_map, - .. - } = options; - - let src_root = match krate.src { - FileName::Real(ref p) => match p.local_path().parent() { - Some(p) => p.to_path_buf(), - None => PathBuf::new(), - }, - _ => PathBuf::new(), - }; - // If user passed in `--playground-url` arg, we fill in crate name here - let mut playground = None; - if let Some(url) = playground_url { - playground = - Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url }); - } - let mut layout = layout::Layout { - logo: String::new(), - favicon: String::new(), - external_html, - default_settings, - krate: krate.name.to_string(), - 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.to_string()), - url: s.to_string(), - }); - } - (sym::issue_tracker_base_url, Some(s)) => { - issue_tracker_base_url = Some(s.to_string()); - } - (sym::html_no_source, None) if attr.is_word() => { - include_sources = false; - } - _ => {} - } - } - } - let (sender, receiver) = channel(); - let mut scx = SharedContext { - tcx, - collapsed: krate.collapsed, - src_root, - include_sources, - local_sources: Default::default(), - issue_tracker_base_url, - layout, - created_dirs: Default::default(), - sort_modules_alphabetically, - style_files, - resource_suffix, - static_root_path, - fs: DocFS::new(sender), - edition, - codes: ErrorCodes::from(unstable_features.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, &mut cache, tcx); - - let mut cx = Context { - current: Vec::new(), - dst, - render_redirect_pages: false, - id_map: Rc::new(RefCell::new(id_map)), - deref_id_map: Rc::new(RefCell::new(FxHashMap::default())), - shared: Arc::new(scx), - all: Rc::new(RefCell::new(AllTypes::new())), - errors: Rc::new(receiver), - cache: Rc::new(cache), - redirections: if generate_redirect_map { Some(Default::default()) } else { None }, - }; - - 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_krate( - &mut self, - krate: &clean::Crate, - diag: &rustc_errors::Handler, - ) -> Result<(), Error> { - let final_file = self.dst.join(&*krate.name.as_str()).join("all.html"); - let settings_file = self.dst.join("settings.html"); - let crate_name = krate.name; - - 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, - &self.shared.style_files, - )?, - &style_files, - ); - self.shared.fs.write(&settings_file, v.as_bytes())?; - if let Some(redirections) = self.redirections.take() { - if !redirections.borrow().is_empty() { - let redirect_map_path = - self.dst.join(&*krate.name.as_str()).join("redirect-map.json"); - let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); - self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?; - self.shared.fs.write(&redirect_map_path, paths.as_bytes())?; - } - } - - // Flush pending errors. - Arc::get_mut(&mut self.shared).unwrap().fs.close(); - let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count(); - if nb_errors > 0 { - Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) - } else { - Ok(()) - } - } - - fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> 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 module = match *item.kind { - clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m, - _ => unreachable!(), - }; - let items = self.build_sidebar_items(module); - let js_dst = self.dst.join("sidebar-items.js"); - let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap()); - scx.fs.write(&js_dst, &v)?; - } - Ok(()) - } - - fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> { - info!("Recursed; leaving {}", self.dst.display()); - - // Go back to where we were at - self.dst.pop(); - self.current.pop(); - Ok(()) - } - - fn item(&mut self, item: clean::Item) -> 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.as_str()); - 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); - if let Some(ref redirections) = self.redirections { - let crate_name = &self.shared.layout.krate; - redirections.borrow_mut().insert( - format!("{}/{}", crate_name, redir_name), - format!("{}/{}", crate_name, file_name), - ); - } else { - let v = layout::redirect(file_name); - let redir_dst = self.dst.join(redir_name); - self.shared.fs.write(&redir_dst, v.as_bytes())?; - } - } - } - Ok(()) - } - - fn cache(&self) -> &Cache { - &self.cache - } -} - -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.svg"), static_files::RUST_FAVICON_SVG)?; - write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?; - write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?; - } - 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 !== "theme-picker" && - (!active.parentNode || active.parentNode.id !== "theme-choices") && - (!related || - (related.id !== "theme-picker" && - (!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); - useSystemTheme(false); - }}; - 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.woff2"), static_files::fira_sans::REGULAR2)?; - write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?; - 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() { - let prefix = format!(r#"{}["{}"]"#, key, krate); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with(key) { - continue; - } - if line.starts_with(&prefix) { - 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() { - let prefix = format!("\"{}\"", krate); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with('"') { - continue; - } - if line.starts_with(&prefix) { - 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(); - 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 = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e)); - } - } - } - - let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix)); - let (mut all_sources, _krates) = - try_err!(collect(&dst, &krate.name.as_str(), "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 and crate list. - 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.as_str()), &dst); - all_indexes.push(search_index); - krates.push(krate.name.to_string()); - krates.sort(); - - // 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")); - v.push_str("\\\n}');\ninitSearch(searchIndex);"); - cx.shared.fs.write(&dst, &v)?; - } - - let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix)); - let crate_list = - format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(",")); - cx.shared.fs.write(&crate_list_dst, &crate_list)?; - - 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: &[], - }; - - 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(cx.cache(), false).to_string(), - synthetic: imp.inner_impl().synthetic, - types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()), - }) - } - }) - .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.as_str(), "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()) - } -} - fn write_srclink(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { if let Some(l) = cx.src_href(item) { write!(buf, "[src]", l) @@ -1541,390 +618,68 @@ fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result { - 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 document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) { + if let Some(ref name) = item.name { + info!("Documenting {}", name); } + document_item_info(w, cx, item, false, parent); + document_full(w, item, cx, "", false); +} - 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()); - }); +/// Render md_text as markdown. +fn render_markdown( + w: &mut Buffer, + cx: &Context<'_>, + md_text: &str, + links: Vec, + 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() + ) +} - 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.unwrap().as_str()); - } - title.push_str(" - Rust"); - let tyname = it.type_(); - let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc)); - let desc = if let Some(desc) = desc { - desc - } else 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: &[], - }; +/// Writes a documentation block containing only the first paragraph of the documentation. If the +/// docs are longer, a "Read more" link is appended to the end. +fn document_short( + w: &mut Buffer, + item: &clean::Item, + cx: &Context<'_>, + link: AssocItemLink<'_>, + prefix: &str, + is_hidden: bool, + parent: Option<&clean::Item>, + show_def_docs: bool, +) { + document_item_info(w, cx, item, is_hidden, parent); + if !show_def_docs { + return; + } + if let Some(s) = item.doc_value() { + let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string(); - { - self.id_map.borrow_mut().reset(); - self.id_map.borrow_mut().populate(&INITIAL_IDS); - } + if s.contains('\n') { + let link = + format!(r#" Read more"#, naive_assoc_href(item, link, cx.cache())); - 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 { - if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) { - let mut path = String::new(); - for name in &names[..names.len() - 1] { - path.push_str(name); - path.push('/'); - } - path.push_str(&item_path(ty, names.last().unwrap())); - match self.redirections { - Some(ref redirections) => { - let mut current_path = String::new(); - for name in &self.current { - current_path.push_str(name); - current_path.push('/'); - } - current_path.push_str(&item_path(ty, names.last().unwrap())); - redirections.borrow_mut().insert(current_path, path); - } - None => return layout::redirect(&format!("{}{}", self.root_path(), path)), - } - } - String::new() - } - } - - /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. - 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(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))), - )); - } - - 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) -> Option { - if item.source.is_dummy() { - return None; - } - let mut root = self.root_path(); - let mut path = String::new(); - let cnum = item.source.cnum(self.sess()); - - // We can safely ignore synthetic `SourceFile`s. - let file = match item.source.filename(self.sess()) { - FileName::Real(ref path) => path.local_path().to_path_buf(), - _ => return None, - }; - let file = &file; - - let symbol; - let (krate, path) = if cnum == LOCAL_CRATE { - if let Some(path) = self.shared.local_sources.get(file) { - (self.shared.layout.krate.as_str(), path) - } else { - return None; - } - } else { - let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? { - (name, ref src, ExternalLocation::Local) => (name, src), - (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()); - symbol = krate.as_str(); - (&*symbol, &path) - }; - - let loline = item.source.lo(self.sess()).line; - let hiline = item.source.hi(self.sess()).line; - let lines = - if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, 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), -{ - w.write_str("
    "); - f(w); - w.write_str("
    ") -} - -fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { - debug_assert!(!item.is_stripped()); - // Write the breadcrumb trail header for the top - buf.write_str("

    "); - let name = match *item.kind { - 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()); - - buf.write_str(""); // in-band - buf.write_str(""); - render_stability_since_raw( - buf, - item.stable_since(cx.tcx()).as_deref(), - item.const_stable_since(cx.tcx()).as_deref(), - None, - None, - ); - buf.write_str( - "\ - \ - []\ - \ - ", - ); - - // Write `src` tag - // - // When this item is part of a `crate 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() { - write_srclink(cx, item, buf); - } - - buf.write_str("

    "); // out-of-band - - match *item.kind { - 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.unwrap().as_str()); - s -} - -fn document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) { - if let Some(ref name) = item.name { - info!("Documenting {}", name); - } - document_item_info(w, cx, item, false, parent); - document_full(w, item, cx, "", false); -} - -/// Render md_text as markdown. -fn render_markdown( - w: &mut Buffer, - cx: &Context<'_>, - md_text: &str, - links: Vec, - 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() - ) -} - -/// Writes a documentation block containing only the first paragraph of the documentation. If the -/// docs are longer, a "Read more" link is appended to the end. -fn document_short( - w: &mut Buffer, - item: &clean::Item, - cx: &Context<'_>, - link: AssocItemLink<'_>, - prefix: &str, - is_hidden: bool, - parent: Option<&clean::Item>, - show_def_docs: bool, -) { - document_item_info(w, cx, item, is_hidden, parent); - if !show_def_docs { - return; - } - if let Some(s) = item.doc_value() { - let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string(); - - if s.contains('\n') { - let link = - format!(r#" Read more"#, naive_assoc_href(item, link, cx.cache())); - - if let Some(idx) = summary_html.rfind("

    ") { - summary_html.insert_str(idx, &link); - } else { - summary_html.push_str(&link); + if let Some(idx) = summary_html.rfind("

    ") { + summary_html.insert_str(idx, &link); + } else { + summary_html.push_str(&link); } } @@ -1992,324 +747,6 @@ fn document_item_info( } } -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() { - w.write_str( - "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() { - w.write_str( - "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() { - w.write_str( - "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 { - w.write_str( - "This type will require a wildcard arm in any match statements or constructors.", - ); - } - - w.write_str("
    "); - } -} - -/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). -crate fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering { - /// Takes a non-numeric and a numeric part from the given &str. - fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) { - let i = s.find(|c: char| c.is_ascii_digit()); - let (a, b) = s.split_at(i.unwrap_or(s.len())); - let i = b.find(|c: char| !c.is_ascii_digit()); - let (b, c) = b.split_at(i.unwrap_or(b.len())); - *s = c; - (a, b) - } - - while !lhs.is_empty() || !rhs.is_empty() { - let (la, lb) = take_parts(&mut lhs); - let (ra, rb) = take_parts(&mut rhs); - // First process the non-numeric part. - match la.cmp(ra) { - Ordering::Equal => (), - x => return x, - } - // Then process the numeric part, if both sides have one (and they fit in a u64). - if let (Ok(ln), Ok(rn)) = (lb.parse::(), rb.parse::()) { - match ln.cmp(&rn) { - Ordering::Equal => (), - x => return x, - } - } - // Then process the numeric part again, but this time as strings. - match lb.cmp(rb) { - Ordering::Equal => (), - x => return x, - } - } - - Ordering::Equal -} - -fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { - document(w, cx, item, None); - - 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, - tcx: TyCtxt<'_>, - ) -> Ordering { - let ty1 = i1.type_(); - let ty2 = i2.type_(); - if ty1 != ty2 { - return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)); - } - let s1 = i1.stability(tcx).as_ref().map(|s| s.level); - let s2 = i2.stability(tcx).as_ref().map(|s| s.level); - if let (Some(a), Some(b)) = (s1, s2) { - match (a.is_stable(), b.is_stable()) { - (true, true) | (false, false) => {} - (false, true) => return Ordering::Less, - (true, false) => return Ordering::Greater, - } - } - let lhs = i1.name.unwrap_or(kw::Empty).as_str(); - let rhs = i2.name.unwrap_or(kw::Empty).as_str(); - compare_names(&lhs, &rhs) - } - - if cx.shared.sort_modules_alphabetically { - indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2, cx.tcx())); - } - // This call is to remove re-export duplicates in cases such as: - // - // ``` - // crate mod foo { - // crate mod bar { - // crate trait Double { fn foo(); } - // } - // } - // - // crate use foo::bar::*; - // crate 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() { - w.write_str(""); - } - 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.kind { - 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(cx.tcx(), myitem.def_id, cx.cache()), - import.print(cx.cache()) - ); - } - - _ => { - if myitem.name.is_none() { - continue; - } - - let unsafety_flag = match *myitem.kind { - clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func) - if func.header.unsafety == hir::Unsafety::Unsafe => - { - "" - } - _ => "", - }; - - let stab = myitem.stability_class(cx.tcx()); - let add = if stab.is_some() { " " } else { "" }; - - let doc_value = myitem.doc_value().unwrap_or_default(); - write!( - w, - "\ - \ - \ - ", - name = *myitem.name.as_ref().unwrap(), - stab_tags = extra_info_tags(myitem, item, cx.tcx()), - docs = MarkdownSummaryLine(&doc_value, &myitem.links(&cx.cache)).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.unwrap().as_str()), - 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() { - w.write_str("
    {}extern crate {} as {};", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - anchor(myitem.def_id, &*src.as_str(), cx.cache()), - name - ), - None => write!( - w, - "
    {}extern crate {};", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - anchor(myitem.def_id, &*name.as_str(), cx.cache()) - ), - } - w.write_str("
    {}{}
    {name}{unsafety_flag}{stab_tags}{docs}
    "); - } -} - -/// Render the stability, deprecation and portability tags that are displayed in the item's summary -/// at the module level. -fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> String { - let mut tags = String::new(); - - fn tag_html(class: &str, title: &str, contents: &str) -> String { - format!(r#"{}"#, class, Escape(title), contents) - } - - // The trailing space after each tag is to space it properly against the rest of the docs. - if let Some(depr) = &item.deprecation(tcx) { - let mut message = "Deprecated"; - if !stability::deprecation_in_effect( - depr.is_since_rustc_version, - depr.since.map(|s| s.as_str()).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(tcx) - .as_ref() - .map(|s| s.level.is_unstable() && s.feature != sym::rustc_private) - == Some(true) - { - tags += &tag_html("unstable", "", "Experimental"); - } - - let cfg = match (&item.attrs.cfg, parent.attrs.cfg.as_ref()) { - (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), - (cfg, _) => cfg.as_deref().cloned(), - }; - - debug!("Portability {:?} - {:?} = {:?}", item.attrs.cfg, parent.attrs.cfg, cfg); - if let Some(ref cfg) = cfg { - tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html()); - } - - tags -} - fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option { let cfg = match (&item.attrs.cfg, parent.and_then(|p| p.attrs.cfg.as_ref())) { (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), @@ -2394,504 +831,66 @@ fn short_item_info( )); } - message.push_str(&format!(" ({})", feature)); - - if let Some(unstable_reason) = reason { - let mut ids = cx.id_map.borrow_mut(); - message = format!( - "
    {}{}
    ", - message, - MarkdownHtml( - &unstable_reason.as_str(), - &mut ids, - error_codes, - cx.shared.edition, - &cx.shared.playground, - ) - .into_string() - ); - } - - extra_info.push(format!("
    {}
    ", message)); - } - - if let Some(portability) = portability(item, parent) { - extra_info.push(portability); - } - - extra_info -} - -fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::Constant) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -
    -    write!(
    -        w,
    -        "{vis}const {name}: {typ}",
    -        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    -        name = it.name.as_ref().unwrap(),
    -        typ = c.type_.print(cx.cache()),
    -    );
    -
    -    if c.value.is_some() || c.is_literal {
    -        write!(w, " = {expr};", expr = Escape(&c.expr));
    -    } else {
    -        w.write_str(";");
    -    }
    -
    -    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));
    -            }
    -        }
    -    }
    -
    -    w.write_str("
    "); - document(w, cx, it, None) -} - -fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Static) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "{vis}static {mutability}{name}: {typ}
    ", - vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - mutability = s.mutability.print_with_space(), - name = it.name.as_ref().unwrap(), - typ = s.type_.print(cx.cache()) - ); - document(w, cx, it, None) -} - -fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { - let header_len = format!( - "{}{}{}{}{:#}fn {}{:#}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - 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(cx.cache()) - ) - .len(); - w.write_str("
    ");
    -    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(cx.tcx(), it.def_id, cx.cache()), - 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(cx.cache()), - where_clause = - WhereClause { gens: &f.generics, indent: 0, end_newline: true }.print(cx.cache()), - decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness } - .print(cx.cache()), - spotlight = spotlight_decl(&f.decl, cx.cache()), - ); - document(w, cx, it, None) -} - -fn render_implementor( - cx: &Context<'_>, - implementor: &Impl, - trait_: &clean::Item, - w: &mut Buffer, - implementor_dups: &FxHashMap, - 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()].1, - _ => false, - }; - render_impl( - w, - cx, - implementor, - trait_, - AssocItemLink::Anchor(None), - RenderMode::Normal, - trait_.stable_since(cx.tcx()).as_deref(), - trait_.const_stable_since(cx.tcx()).as_deref(), - 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_full(cx.cache()).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, - containing_item, - assoc_link, - RenderMode::Normal, - containing_item.stable_since(cx.tcx()).as_deref(), - containing_item.const_stable_since(cx.tcx()).as_deref(), - true, - None, - false, - true, - &[], - ); - buffer.into_inner() - }) - .collect::>(); - impls.sort(); - w.write_str(&impls.join("")); -} - -fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cache: &Cache) -> 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(cache).to_string()); - } - } - bounds -} - -fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cache: &Cache) -> Ordering { - let lhs = format!("{}", lhs.inner_impl().print(cache, false)); - let rhs = format!("{}", rhs.inner_impl().print(cache, false)); - - // lhs and rhs are formatted as HTML, which may be unnecessary - compare_names(&lhs, &rhs) -} - -fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { - let bounds = bounds(&t.bounds, false, cx.cache()); - 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| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        write!(
    -            w,
    -            "{}{}{}trait {}{}{}",
    -            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    -            t.unsafety.print_with_space(),
    -            if t.is_auto { "auto " } else { "" },
    -            it.name.as_ref().unwrap(),
    -            t.generics.print(cx.cache()),
    -            bounds
    -        );
    -
    -        if !t.generics.where_predicates.is_empty() {
    -            let where_ = WhereClause { gens: &t.generics, indent: 0, end_newline: true };
    -            write!(w, "{}", where_.print(cx.cache()));
    -        } else {
    -            w.write_str(" ");
    -        }
    -
    -        if t.items.is_empty() {
    -            w.write_str("{ }");
    -        } else {
    -            // FIXME: we should be using a derived_id for the Anchors here
    -            w.write_str("{\n");
    -            for t in &types {
    -                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    -                w.write_str(";\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, cx);
    -                w.write_str(";\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, cx);
    -                w.write_str(";\n");
    -
    -                if pos < required.len() - 1 {
    -                    w.write_str("
    "); - } - } - 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, cx); - match *m.kind { - clean::MethodItem(ref inner, _) - if !inner.generics.where_predicates.is_empty() => - { - w.write_str(",\n { ... }\n"); - } - _ => { - w.write_str(" { ... }\n"); - } - } - if pos < provided.len() - 1 { - w.write_str("
    "); - } - } - w.write_str("}"); - } - w.write_str("
    ") - }); - - // Trait documentation - document(w, cx, it, None); - - 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(); - info!("Documenting {} on {:?}", name, t.name); - 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, cx); - w.write_str(""); - render_stability_since(w, m, t, cx.tcx()); - write_srclink(cx, m, w); - w.write_str("

    "); - document(w, cx, m, Some(t)); - } - - 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 = 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()).or_insert((did, false)); - if prev_did != did { - *has_duplicates = true; - } - } - _ => {} - } - } - - let (local, foreign) = implementors.iter().partition::, _>(|i| { - i.inner_impl() - .for_ - .def_id_full(cx.cache()) - .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(|a, b| compare_impl(a, b, cx.cache())); - concrete.sort_by(|a, b| compare_impl(a, b, cx.cache())); - - 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, - it, - assoc_link, - RenderMode::Normal, - implementor.impl_item.stable_since(cx.tcx()).as_deref(), - implementor.impl_item.const_stable_since(cx.tcx()).as_deref(), - false, - None, - true, - false, - &[], - ); - } - write_loading_content(w, ""); - } - - write_small_section_header( - w, - "implementors", - "Implementors", - "
    ", - ); - for implementor in concrete { - render_implementor(cx, implementor, it, w, &implementor_dups, &[]); - } - write_loading_content(w, "
    "); - - if t.is_auto { - write_small_section_header( - w, - "synthetic-implementors", - "Auto implementors", - "
    ", - ); - for implementor in synthetic { - render_implementor( - cx, - implementor, - it, - w, - &implementor_dups, - &collect_paths_for_type(implementor.inner_impl().for_.clone(), &cx.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, "
    "); + message.push_str(&format!(" ({})", feature)); - if t.is_auto { - write_small_section_header( - w, - "synthetic-implementors", - "Auto implementors", - "
    ", + if let Some(unstable_reason) = reason { + let mut ids = cx.id_map.borrow_mut(); + message = format!( + "
    {}{}
    ", + message, + MarkdownHtml( + &unstable_reason.as_str(), + &mut ids, + error_codes, + cx.shared.edition, + &cx.shared.playground, + ) + .into_string() ); - write_loading_content(w, "
    "); } + + extra_info.push(format!("
    {}
    ", message)); } - 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() - ); + if let Some(portability) = portability(item, parent) { + extra_info.push(portability); + } + + extra_info +} + +fn render_impls( + cx: &Context<'_>, + w: &mut Buffer, + traits: &[&&Impl], + containing_item: &clean::Item, +) { + let mut impls = traits + .iter() + .map(|i| { + let did = i.trait_did_full(cx.cache()).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, + containing_item, + assoc_link, + RenderMode::Normal, + containing_item.stable_since(cx.tcx()).as_deref(), + containing_item.const_stable_since(cx.tcx()).as_deref(), + true, + None, + false, + true, + &[], + ); + buffer.into_inner() + }) + .collect::>(); + impls.sort(); + w.write_str(&impls.join("")); } fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cache: &Cache) -> String { @@ -2986,21 +985,6 @@ fn render_stability_since_raw( } } -fn render_stability_since( - w: &mut Buffer, - item: &clean::Item, - containing_item: &clean::Item, - tcx: TyCtxt<'_>, -) { - render_stability_since_raw( - w, - item.stable_since(tcx).as_deref(), - item.const_stable_since(tcx).as_deref(), - containing_item.stable_since(tcx).as_deref(), - containing_item.const_stable_since(tcx).as_deref(), - ) -} - fn render_assoc_item( w: &mut Buffer, item: &clean::Item, @@ -3104,227 +1088,6 @@ fn render_assoc_item( } } -fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) { - wrap_into_docblock(w, |w| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
    -        w.write_str("
    ") - }); - - document(w, cx, it, None); - let mut fields = s - .fields - .iter() - .filter_map(|f| match *f.kind { - clean::StructFieldItem(ref ty) => Some((f, ty)), - _ => None, - }) - .peekable(); - if let CtorKind::Fictive = 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(cx.cache()) - ); - document(w, cx, field, Some(it)); - } - } - } - 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| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        render_union(w, it, Some(&s.generics), &s.fields, "", true, cx);
    -        w.write_str("
    ") - }); - - document(w, cx, it, None); - let mut fields = s - .fields - .iter() - .filter_map(|f| match *f.kind { - 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(cx.cache()) - ); - if let Some(stability_class) = field.stability_class(cx.tcx()) { - write!(w, "", stab = stability_class); - } - document(w, cx, field, Some(it)); - } - } - 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| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        write!(
    -            w,
    -            "{}enum {}{}{}",
    -            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    -            it.name.as_ref().unwrap(),
    -            e.generics.print(cx.cache()),
    -            WhereClause { gens: &e.generics, indent: 0, end_newline: true }.print(cx.cache())
    -        );
    -        if e.variants.is_empty() && !e.variants_stripped {
    -            w.write_str(" {}");
    -        } else {
    -            w.write_str(" {\n");
    -            for v in &e.variants {
    -                w.write_str("    ");
    -                let name = v.name.as_ref().unwrap();
    -                match *v.kind {
    -                    clean::VariantItem(ref var) => match var {
    -                        clean::Variant::CLike => write!(w, "{}", name),
    -                        clean::Variant::Tuple(ref tys) => {
    -                            write!(w, "{}(", name);
    -                            for (i, ty) in tys.iter().enumerate() {
    -                                if i > 0 {
    -                                    w.write_str(", ")
    -                                }
    -                                write!(w, "{}", ty.print(cx.cache()));
    -                            }
    -                            w.write_str(")");
    -                        }
    -                        clean::Variant::Struct(ref s) => {
    -                            render_struct(w, v, None, s.struct_type, &s.fields, "    ", false, cx);
    -                        }
    -                    },
    -                    _ => unreachable!(),
    -                }
    -                w.write_str(",\n");
    -            }
    -
    -            if e.variants_stripped {
    -                w.write_str("    // some variants omitted\n");
    -            }
    -            w.write_str("}");
    -        }
    -        w.write_str("
    ") - }); - - document(w, cx, it, None); - 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(clean::Variant::Tuple(ref tys)) = *variant.kind { - w.write_str("("); - for (i, ty) in tys.iter().enumerate() { - if i > 0 { - w.write_str(", "); - } - write!(w, "{}", ty.print(cx.cache())); - } - w.write_str(")"); - } - w.write_str("
    "); - document(w, cx, variant, Some(it)); - document_non_exhaustive(w, variant); - - use crate::clean::Variant; - if let clean::VariantItem(Variant::Struct(ref s)) = *variant.kind { - 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.kind { - 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(cx.cache()) - ); - document(w, cx, field, Some(variant)); - } - } - w.write_str("
    "); - } - render_stability_since(w, variant, it, cx.tcx()); - } - } - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - const ALLOWED_ATTRIBUTES: &[Symbol] = &[ sym::export_name, sym::lang, @@ -3367,147 +1130,6 @@ fn render_attributes(w: &mut Buffer, it: &clean::Item, top: bool) { } } -fn render_struct( - w: &mut Buffer, - it: &clean::Item, - g: Option<&clean::Generics>, - ty: CtorKind, - fields: &[clean::Item], - tab: &str, - structhead: bool, - cx: &Context<'_>, -) { - write!( - w, - "{}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - if structhead { "struct " } else { "" }, - it.name.as_ref().unwrap() - ); - if let Some(g) = g { - write!(w, "{}", g.print(cx.cache())) - } - match ty { - CtorKind::Fictive => { - if let Some(g) = g { - write!( - w, - "{}", - WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache()) - ) - } - let mut has_visible_fields = false; - w.write_str(" {"); - for field in fields { - if let clean::StructFieldItem(ref ty) = *field.kind { - write!( - w, - "\n{} {}{}: {},", - tab, - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), - field.name.as_ref().unwrap(), - ty.print(cx.cache()) - ); - 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 */ "); - } - w.write_str("}"); - } - CtorKind::Fn => { - w.write_str("("); - for (i, field) in fields.iter().enumerate() { - if i > 0 { - w.write_str(", "); - } - match *field.kind { - clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), - clean::StructFieldItem(ref ty) => { - write!( - w, - "{}{}", - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), - ty.print(cx.cache()) - ) - } - _ => unreachable!(), - } - } - w.write_str(")"); - if let Some(g) = g { - write!( - w, - "{}", - WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) - ) - } - w.write_str(";"); - } - CtorKind::Const => { - // Needed for PhantomData. - if let Some(g) = g { - write!( - w, - "{}", - WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) - ) - } - w.write_str(";"); - } - } -} - -fn render_union( - w: &mut Buffer, - it: &clean::Item, - g: Option<&clean::Generics>, - fields: &[clean::Item], - tab: &str, - structhead: bool, - cx: &Context<'_>, -) { - write!( - w, - "{}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - if structhead { "union " } else { "" }, - it.name.as_ref().unwrap() - ); - if let Some(g) = g { - write!(w, "{}", g.print(cx.cache())); - write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache())); - } - - write!(w, " {{\n{}", tab); - for field in fields { - if let clean::StructFieldItem(ref ty) = *field.kind { - write!( - w, - " {}{}: {},\n{}", - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), - field.name.as_ref().unwrap(), - ty.print(cx.cache()), - tab - ); - } - } - - if it.has_stripped_fields().unwrap() { - write!(w, " // some fields omitted\n{}", tab); - } - w.write_str("}"); -} - #[derive(Copy, Clone)] enum AssocItemLink<'a> { Anchor(Option<&'a str>), @@ -4078,86 +1700,6 @@ fn render_impl( w.write_str(""); } -fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "type {}{}{where_clause} = impl {bounds};
    ", - it.name.as_ref().unwrap(), - t.generics.print(cx.cache()), - where_clause = - WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), - bounds = bounds(&t.bounds, false, cx.cache()) - ); - - document(w, cx, it, None); - - // 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) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "trait {}{}{} = {};
    ", - it.name.as_ref().unwrap(), - t.generics.print(cx.cache()), - WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), - bounds(&t.bounds, true, cx.cache()) - ); - - document(w, cx, it, None); - - // 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) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "type {}{}{where_clause} = {type_};
    ", - it.name.as_ref().unwrap(), - t.generics.print(cx.cache()), - where_clause = - WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), - type_ = t.type_.print(cx.cache()) - ); - - document(w, cx, it, None); - - // 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) { - w.write_str("
    extern {\n");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "    {}type {};\n}}
    ", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - it.name.as_ref().unwrap(), - ); - - document(w, cx, it, None); - - 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 }; @@ -4850,65 +2392,8 @@ fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { } } -fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) { - wrap_into_docblock(w, |w| { - highlight::render_with_highlighting( - &t.source, - w, - Some("macro"), - None, - None, - it.source.span().edition(), - ); - }); - document(w, cx, it, None) -} - -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 => { - w.push_str("
    ");
    -            write!(w, "{}!() {{ /* proc-macro */ }}", name);
    -            w.push_str("
    "); - } - MacroKind::Attr => { - w.push_str("
    ");
    -            write!(w, "#[{}]", name);
    -            w.push_str("
    "); - } - MacroKind::Derive => { - w.push_str("
    ");
    -            write!(w, "#[derive({})]", name);
    -            if !m.helpers.is_empty() {
    -                w.push_str("\n{\n");
    -                w.push_str("    // Attributes available to this derive:\n");
    -                for attr in &m.helpers {
    -                    writeln!(w, "    #[{}]", attr);
    -                }
    -                w.push_str("}\n");
    -            }
    -            w.push_str("
    "); - } - } - document(w, cx, it, None) -} - -fn item_primitive(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { - document(w, cx, it, None); - 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, None) -} - 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 diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs new file mode 100644 index 0000000000000..6d61248f28ef0 --- /dev/null +++ b/src/librustdoc/html/render/print_item.rs @@ -0,0 +1,1420 @@ +use std::cmp::Ordering; + +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::def::CtorKind; +use rustc_hir::def_id::DefId; +use rustc_middle::middle::stability; +use rustc_middle::ty::TyCtxt; +use rustc_span::hygiene::MacroKind; +use rustc_span::symbol::{kw, sym, Symbol}; + +use super::{ + collect_paths_for_type, document, ensure_trailing_slash, item_ty_to_strs, render_assoc_item, + render_assoc_items, render_attributes, render_impl, render_stability_since_raw, spotlight_decl, + write_srclink, AssocItemLink, Context, +}; +use crate::clean::{self, GetDefId}; +use crate::formats::cache::Cache; +use crate::formats::item_type::ItemType; +use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode}; +use crate::html::escape::Escape; +use crate::html::format::{print_abi_with_space, Buffer, Function, PrintWithSpace, WhereClause}; +use crate::html::highlight; +use crate::html::markdown::MarkdownSummaryLine; + +pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { + debug_assert!(!item.is_stripped()); + // Write the breadcrumb trail header for the top + buf.write_str("

    "); + let name = match *item.kind { + 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()); + + buf.write_str(""); // in-band + buf.write_str(""); + render_stability_since_raw( + buf, + item.stable_since(cx.tcx()).as_deref(), + item.const_stable_since(cx.tcx()).as_deref(), + None, + None, + ); + buf.write_str( + "\ + \ + []\ + \ + ", + ); + + // Write `src` tag + // + // When this item is part of a `crate 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() { + write_srclink(cx, item, buf); + } + + buf.write_str("

    "); // out-of-band + + match *item.kind { + 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_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { + document(w, cx, item, None); + + 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, + tcx: TyCtxt<'_>, + ) -> Ordering { + let ty1 = i1.type_(); + let ty2 = i2.type_(); + if ty1 != ty2 { + return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)); + } + let s1 = i1.stability(tcx).as_ref().map(|s| s.level); + let s2 = i2.stability(tcx).as_ref().map(|s| s.level); + if let (Some(a), Some(b)) = (s1, s2) { + match (a.is_stable(), b.is_stable()) { + (true, true) | (false, false) => {} + (false, true) => return Ordering::Less, + (true, false) => return Ordering::Greater, + } + } + let lhs = i1.name.unwrap_or(kw::Empty).as_str(); + let rhs = i2.name.unwrap_or(kw::Empty).as_str(); + compare_names(&lhs, &rhs) + } + + if cx.shared.sort_modules_alphabetically { + indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2, cx.tcx())); + } + // This call is to remove re-export duplicates in cases such as: + // + // ``` + // crate mod foo { + // crate mod bar { + // crate trait Double { fn foo(); } + // } + // } + // + // crate use foo::bar::*; + // crate 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() { + w.write_str(""); + } + 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.kind { + 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(cx.tcx(), myitem.def_id, cx.cache()), + import.print(cx.cache()) + ); + } + + _ => { + if myitem.name.is_none() { + continue; + } + + let unsafety_flag = match *myitem.kind { + clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func) + if func.header.unsafety == hir::Unsafety::Unsafe => + { + "" + } + _ => "", + }; + + let stab = myitem.stability_class(cx.tcx()); + let add = if stab.is_some() { " " } else { "" }; + + let doc_value = myitem.doc_value().unwrap_or_default(); + write!( + w, + "\ + \ + \ + ", + name = *myitem.name.as_ref().unwrap(), + stab_tags = extra_info_tags(myitem, item, cx.tcx()), + docs = MarkdownSummaryLine(&doc_value, &myitem.links(&cx.cache)).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.unwrap().as_str()), + 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() { + w.write_str("
    {}extern crate {} as {};", + myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), + anchor(myitem.def_id, &*src.as_str(), cx.cache()), + name + ), + None => write!( + w, + "
    {}extern crate {};", + myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), + anchor(myitem.def_id, &*name.as_str(), cx.cache()) + ), + } + w.write_str("
    {}{}
    {name}{unsafety_flag}{stab_tags}{docs}
    "); + } +} + +/// Render the stability, deprecation and portability tags that are displayed in the item's summary +/// at the module level. +fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> String { + let mut tags = String::new(); + + fn tag_html(class: &str, title: &str, contents: &str) -> String { + format!(r#"{}"#, class, Escape(title), contents) + } + + // The trailing space after each tag is to space it properly against the rest of the docs. + if let Some(depr) = &item.deprecation(tcx) { + let mut message = "Deprecated"; + if !stability::deprecation_in_effect( + depr.is_since_rustc_version, + depr.since.map(|s| s.as_str()).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(tcx) + .as_ref() + .map(|s| s.level.is_unstable() && s.feature != sym::rustc_private) + == Some(true) + { + tags += &tag_html("unstable", "", "Experimental"); + } + + let cfg = match (&item.attrs.cfg, parent.attrs.cfg.as_ref()) { + (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), + (cfg, _) => cfg.as_deref().cloned(), + }; + + debug!("Portability {:?} - {:?} = {:?}", item.attrs.cfg, parent.attrs.cfg, cfg); + if let Some(ref cfg) = cfg { + tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html()); + } + + tags +} + +fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { + let header_len = format!( + "{}{}{}{}{:#}fn {}{:#}", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + 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(cx.cache()) + ) + .len(); + w.write_str("
    ");
    +    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(cx.tcx(), it.def_id, cx.cache()), + 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(cx.cache()), + where_clause = + WhereClause { gens: &f.generics, indent: 0, end_newline: true }.print(cx.cache()), + decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness } + .print(cx.cache()), + spotlight = spotlight_decl(&f.decl, cx.cache()), + ); + document(w, cx, it, None) +} + +fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { + let bounds = bounds(&t.bounds, false, cx.cache()); + 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| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        write!(
    +            w,
    +            "{}{}{}trait {}{}{}",
    +            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    +            t.unsafety.print_with_space(),
    +            if t.is_auto { "auto " } else { "" },
    +            it.name.as_ref().unwrap(),
    +            t.generics.print(cx.cache()),
    +            bounds
    +        );
    +
    +        if !t.generics.where_predicates.is_empty() {
    +            let where_ = WhereClause { gens: &t.generics, indent: 0, end_newline: true };
    +            write!(w, "{}", where_.print(cx.cache()));
    +        } else {
    +            w.write_str(" ");
    +        }
    +
    +        if t.items.is_empty() {
    +            w.write_str("{ }");
    +        } else {
    +            // FIXME: we should be using a derived_id for the Anchors here
    +            w.write_str("{\n");
    +            for t in &types {
    +                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    +                w.write_str(";\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, cx);
    +                w.write_str(";\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, cx);
    +                w.write_str(";\n");
    +
    +                if pos < required.len() - 1 {
    +                    w.write_str("
    "); + } + } + 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, cx); + match *m.kind { + clean::MethodItem(ref inner, _) + if !inner.generics.where_predicates.is_empty() => + { + w.write_str(",\n { ... }\n"); + } + _ => { + w.write_str(" { ... }\n"); + } + } + if pos < provided.len() - 1 { + w.write_str("
    "); + } + } + w.write_str("}"); + } + w.write_str("
    ") + }); + + // Trait documentation + document(w, cx, it, None); + + 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(); + info!("Documenting {} on {:?}", name, t.name); + 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, cx); + w.write_str(""); + render_stability_since(w, m, t, cx.tcx()); + write_srclink(cx, m, w); + w.write_str("

    "); + document(w, cx, m, Some(t)); + } + + 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 = 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()).or_insert((did, false)); + if prev_did != did { + *has_duplicates = true; + } + } + _ => {} + } + } + + let (local, foreign) = implementors.iter().partition::, _>(|i| { + i.inner_impl() + .for_ + .def_id_full(cx.cache()) + .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(|a, b| compare_impl(a, b, cx.cache())); + concrete.sort_by(|a, b| compare_impl(a, b, cx.cache())); + + 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, + it, + assoc_link, + RenderMode::Normal, + implementor.impl_item.stable_since(cx.tcx()).as_deref(), + implementor.impl_item.const_stable_since(cx.tcx()).as_deref(), + false, + None, + true, + false, + &[], + ); + } + write_loading_content(w, ""); + } + + write_small_section_header( + w, + "implementors", + "Implementors", + "
    ", + ); + for implementor in concrete { + render_implementor(cx, implementor, it, w, &implementor_dups, &[]); + } + write_loading_content(w, "
    "); + + if t.is_auto { + write_small_section_header( + w, + "synthetic-implementors", + "Auto implementors", + "
    ", + ); + for implementor in synthetic { + render_implementor( + cx, + implementor, + it, + w, + &implementor_dups, + &collect_paths_for_type(implementor.inner_impl().for_.clone(), &cx.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.is_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 item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TraitAlias) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "trait {}{}{} = {};
    ", + it.name.as_ref().unwrap(), + t.generics.print(cx.cache()), + WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), + bounds(&t.bounds, true, cx.cache()) + ); + + document(w, cx, it, None); + + // 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_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "type {}{}{where_clause} = impl {bounds};
    ", + it.name.as_ref().unwrap(), + t.generics.print(cx.cache()), + where_clause = + WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), + bounds = bounds(&t.bounds, false, cx.cache()) + ); + + document(w, cx, it, None); + + // 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) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "type {}{}{where_clause} = {type_};
    ", + it.name.as_ref().unwrap(), + t.generics.print(cx.cache()), + where_clause = + WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), + type_ = t.type_.print(cx.cache()) + ); + + document(w, cx, it, None); + + // 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_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) { + wrap_into_docblock(w, |w| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        render_union(w, it, Some(&s.generics), &s.fields, "", true, cx);
    +        w.write_str("
    ") + }); + + document(w, cx, it, None); + let mut fields = s + .fields + .iter() + .filter_map(|f| match *f.kind { + 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(cx.cache()) + ); + if let Some(stability_class) = field.stability_class(cx.tcx()) { + write!(w, "", stab = stability_class); + } + document(w, cx, field, Some(it)); + } + } + 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| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        write!(
    +            w,
    +            "{}enum {}{}{}",
    +            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    +            it.name.as_ref().unwrap(),
    +            e.generics.print(cx.cache()),
    +            WhereClause { gens: &e.generics, indent: 0, end_newline: true }.print(cx.cache())
    +        );
    +        if e.variants.is_empty() && !e.variants_stripped {
    +            w.write_str(" {}");
    +        } else {
    +            w.write_str(" {\n");
    +            for v in &e.variants {
    +                w.write_str("    ");
    +                let name = v.name.as_ref().unwrap();
    +                match *v.kind {
    +                    clean::VariantItem(ref var) => match var {
    +                        clean::Variant::CLike => write!(w, "{}", name),
    +                        clean::Variant::Tuple(ref tys) => {
    +                            write!(w, "{}(", name);
    +                            for (i, ty) in tys.iter().enumerate() {
    +                                if i > 0 {
    +                                    w.write_str(", ")
    +                                }
    +                                write!(w, "{}", ty.print(cx.cache()));
    +                            }
    +                            w.write_str(")");
    +                        }
    +                        clean::Variant::Struct(ref s) => {
    +                            render_struct(w, v, None, s.struct_type, &s.fields, "    ", false, cx);
    +                        }
    +                    },
    +                    _ => unreachable!(),
    +                }
    +                w.write_str(",\n");
    +            }
    +
    +            if e.variants_stripped {
    +                w.write_str("    // some variants omitted\n");
    +            }
    +            w.write_str("}");
    +        }
    +        w.write_str("
    ") + }); + + document(w, cx, it, None); + 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(clean::Variant::Tuple(ref tys)) = *variant.kind { + w.write_str("("); + for (i, ty) in tys.iter().enumerate() { + if i > 0 { + w.write_str(", "); + } + write!(w, "{}", ty.print(cx.cache())); + } + w.write_str(")"); + } + w.write_str("
    "); + document(w, cx, variant, Some(it)); + document_non_exhaustive(w, variant); + + use crate::clean::Variant; + if let clean::VariantItem(Variant::Struct(ref s)) = *variant.kind { + 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.kind { + 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(cx.cache()) + ); + document(w, cx, field, Some(variant)); + } + } + w.write_str("
    "); + } + render_stability_since(w, variant, it, cx.tcx()); + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) { + wrap_into_docblock(w, |w| { + highlight::render_with_highlighting( + &t.source, + w, + Some("macro"), + None, + None, + it.source.span().edition(), + ); + }); + document(w, cx, it, None) +} + +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 => { + w.push_str("
    ");
    +            write!(w, "{}!() {{ /* proc-macro */ }}", name);
    +            w.push_str("
    "); + } + MacroKind::Attr => { + w.push_str("
    ");
    +            write!(w, "#[{}]", name);
    +            w.push_str("
    "); + } + MacroKind::Derive => { + w.push_str("
    ");
    +            write!(w, "#[derive({})]", name);
    +            if !m.helpers.is_empty() {
    +                w.push_str("\n{\n");
    +                w.push_str("    // Attributes available to this derive:\n");
    +                for attr in &m.helpers {
    +                    writeln!(w, "    #[{}]", attr);
    +                }
    +                w.push_str("}\n");
    +            }
    +            w.push_str("
    "); + } + } + document(w, cx, it, None) +} + +fn item_primitive(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { + document(w, cx, it, None); + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::Constant) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +
    +    write!(
    +        w,
    +        "{vis}const {name}: {typ}",
    +        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    +        name = it.name.as_ref().unwrap(),
    +        typ = c.type_.print(cx.cache()),
    +    );
    +
    +    if c.value.is_some() || c.is_literal {
    +        write!(w, " = {expr};", expr = Escape(&c.expr));
    +    } else {
    +        w.write_str(";");
    +    }
    +
    +    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));
    +            }
    +        }
    +    }
    +
    +    w.write_str("
    "); + document(w, cx, it, None) +} + +fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) { + wrap_into_docblock(w, |w| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
    +        w.write_str("
    ") + }); + + document(w, cx, it, None); + let mut fields = s + .fields + .iter() + .filter_map(|f| match *f.kind { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable(); + if let CtorKind::Fictive = 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(cx.cache()) + ); + document(w, cx, field, Some(it)); + } + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Static) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "{vis}static {mutability}{name}: {typ}
    ", + vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + mutability = s.mutability.print_with_space(), + name = it.name.as_ref().unwrap(), + typ = s.type_.print(cx.cache()) + ); + document(w, cx, it, None) +} + +fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { + w.write_str("
    extern {\n");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "    {}type {};\n}}
    ", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.name.as_ref().unwrap(), + ); + + document(w, cx, it, None); + + 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, None) +} + +/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). +crate fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering { + /// Takes a non-numeric and a numeric part from the given &str. + fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) { + let i = s.find(|c: char| c.is_ascii_digit()); + let (a, b) = s.split_at(i.unwrap_or(s.len())); + let i = b.find(|c: char| !c.is_ascii_digit()); + let (b, c) = b.split_at(i.unwrap_or(b.len())); + *s = c; + (a, b) + } + + while !lhs.is_empty() || !rhs.is_empty() { + let (la, lb) = take_parts(&mut lhs); + let (ra, rb) = take_parts(&mut rhs); + // First process the non-numeric part. + match la.cmp(ra) { + Ordering::Equal => (), + x => return x, + } + // Then process the numeric part, if both sides have one (and they fit in a u64). + if let (Ok(ln), Ok(rn)) = (lb.parse::(), rb.parse::()) { + match ln.cmp(&rn) { + Ordering::Equal => (), + x => return x, + } + } + // Then process the numeric part again, but this time as strings. + match lb.cmp(rb) { + Ordering::Equal => (), + x => return x, + } + } + + Ordering::Equal +} + +pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String { + let mut s = cx.current.join("::"); + s.push_str("::"); + s.push_str(&item.name.unwrap().as_str()); + s +} + +pub(super) fn item_path(ty: ItemType, name: &str) -> String { + match ty { + ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)), + _ => format!("{}.{}.html", ty, name), + } +} + +fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cache: &Cache) -> 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(cache).to_string()); + } + } + bounds +} + +fn wrap_into_docblock(w: &mut Buffer, f: F) +where + F: FnOnce(&mut Buffer), +{ + w.write_str("
    "); + f(w); + w.write_str("
    ") +} + +fn render_stability_since( + w: &mut Buffer, + item: &clean::Item, + containing_item: &clean::Item, + tcx: TyCtxt<'_>, +) { + render_stability_since_raw( + w, + item.stable_since(tcx).as_deref(), + item.const_stable_since(tcx).as_deref(), + containing_item.stable_since(tcx).as_deref(), + containing_item.const_stable_since(tcx).as_deref(), + ) +} + +fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cache: &Cache) -> Ordering { + let lhs = format!("{}", lhs.inner_impl().print(cache, false)); + let rhs = format!("{}", rhs.inner_impl().print(cache, false)); + + // lhs and rhs are formatted as HTML, which may be unnecessary + compare_names(&lhs, &rhs) +} + +fn render_implementor( + cx: &Context<'_>, + implementor: &Impl, + trait_: &clean::Item, + w: &mut Buffer, + implementor_dups: &FxHashMap, + 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()].1, + _ => false, + }; + render_impl( + w, + cx, + implementor, + trait_, + AssocItemLink::Anchor(None), + RenderMode::Normal, + trait_.stable_since(cx.tcx()).as_deref(), + trait_.const_stable_since(cx.tcx()).as_deref(), + false, + Some(use_absolute), + false, + false, + aliases, + ); +} + +fn render_union( + w: &mut Buffer, + it: &clean::Item, + g: Option<&clean::Generics>, + fields: &[clean::Item], + tab: &str, + structhead: bool, + cx: &Context<'_>, +) { + write!( + w, + "{}{}{}", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + if structhead { "union " } else { "" }, + it.name.as_ref().unwrap() + ); + if let Some(g) = g { + write!(w, "{}", g.print(cx.cache())); + write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache())); + } + + write!(w, " {{\n{}", tab); + for field in fields { + if let clean::StructFieldItem(ref ty) = *field.kind { + write!( + w, + " {}{}: {},\n{}", + field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + field.name.as_ref().unwrap(), + ty.print(cx.cache()), + tab + ); + } + } + + if it.has_stripped_fields().unwrap() { + write!(w, " // some fields omitted\n{}", tab); + } + w.write_str("}"); +} + +fn render_struct( + w: &mut Buffer, + it: &clean::Item, + g: Option<&clean::Generics>, + ty: CtorKind, + fields: &[clean::Item], + tab: &str, + structhead: bool, + cx: &Context<'_>, +) { + write!( + w, + "{}{}{}", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + if structhead { "struct " } else { "" }, + it.name.as_ref().unwrap() + ); + if let Some(g) = g { + write!(w, "{}", g.print(cx.cache())) + } + match ty { + CtorKind::Fictive => { + if let Some(g) = g { + write!( + w, + "{}", + WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache()) + ) + } + let mut has_visible_fields = false; + w.write_str(" {"); + for field in fields { + if let clean::StructFieldItem(ref ty) = *field.kind { + write!( + w, + "\n{} {}{}: {},", + tab, + field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + field.name.as_ref().unwrap(), + ty.print(cx.cache()) + ); + 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 */ "); + } + w.write_str("}"); + } + CtorKind::Fn => { + w.write_str("("); + for (i, field) in fields.iter().enumerate() { + if i > 0 { + w.write_str(", "); + } + match *field.kind { + clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), + clean::StructFieldItem(ref ty) => { + write!( + w, + "{}{}", + field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + ty.print(cx.cache()) + ) + } + _ => unreachable!(), + } + } + w.write_str(")"); + if let Some(g) = g { + write!( + w, + "{}", + WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) + ) + } + w.write_str(";"); + } + CtorKind::Const => { + // Needed for PhantomData. + if let Some(g) = g { + write!( + w, + "{}", + WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) + ) + } + w.write_str(";"); + } + } +} + +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() { + w.write_str( + "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() { + w.write_str( + "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() { + w.write_str( + "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 { + w.write_str( + "This type will require a wildcard arm in any match statements or constructors.", + ); + } + + w.write_str("
    "); + } +} diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index 224c794fb3b4a..3175fbe5666d8 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -1,4 +1,7 @@ -use super::*; +use std::cmp::Ordering; + +use super::print_item::compare_names; +use super::{AllTypes, Buffer}; #[test] fn test_compare_names() { diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs new file mode 100644 index 0000000000000..cbf0f9a4927c6 --- /dev/null +++ b/src/librustdoc/html/render/write_shared.rs @@ -0,0 +1,542 @@ +use std::ffi::OsStr; +use std::fmt::Write; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::io::{self, BufReader}; +use std::path::{Component, Path, PathBuf}; + +use itertools::Itertools; +use rustc_data_structures::flock; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use serde::Serialize; + +use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS}; +use crate::clean::Crate; +use crate::config::RenderOptions; +use crate::docfs::{DocFS, PathError}; +use crate::error::Error; +use crate::formats::FormatRenderer; +use crate::html::{layout, static_files}; + +pub(super) fn write_shared( + cx: &Context<'_>, + krate: &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.svg"), static_files::RUST_FAVICON_SVG)?; + write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?; + write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?; + } + 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 !== "theme-picker" && + (!active.parentNode || active.parentNode.id !== "theme-choices") && + (!related || + (related.id !== "theme-picker" && + (!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); + useSystemTheme(false); + }}; + 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.woff2"), static_files::fira_sans::REGULAR2)?; + write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?; + 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() { + let prefix = format!(r#"{}["{}"]"#, key, krate); + for line in BufReader::new(File::open(path)?).lines() { + let line = line?; + if !line.starts_with(key) { + continue; + } + if line.starts_with(&prefix) { + 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() { + let prefix = format!("\"{}\"", krate); + for line in BufReader::new(File::open(path)?).lines() { + let line = line?; + if !line.starts_with('"') { + continue; + } + if line.starts_with(&prefix) { + 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(); + 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 = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e)); + } + } + } + + let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix)); + let (mut all_sources, _krates) = + try_err!(collect(&dst, &krate.name.as_str(), "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 and crate list. + 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.as_str()), &dst); + all_indexes.push(search_index); + krates.push(krate.name.to_string()); + krates.sort(); + + // 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")); + v.push_str("\\\n}');\ninitSearch(searchIndex);"); + cx.shared.fs.write(&dst, &v)?; + } + + let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix)); + let crate_list = + format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(",")); + cx.shared.fs.write(&crate_list_dst, &crate_list)?; + + 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: &[], + }; + + 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(cx.cache(), false).to_string(), + synthetic: imp.inner_impl().synthetic, + types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()), + }) + } + }) + .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.as_str(), "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()) + } +}