summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html/render
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /src/librustdoc/html/render
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/librustdoc/html/render')
-rw-r--r--src/librustdoc/html/render/context.rs762
-rw-r--r--src/librustdoc/html/render/mod.rs2849
-rw-r--r--src/librustdoc/html/render/print_item.rs1974
-rw-r--r--src/librustdoc/html/render/search_index.rs589
-rw-r--r--src/librustdoc/html/render/span_map.rs203
-rw-r--r--src/librustdoc/html/render/tests.rs54
-rw-r--r--src/librustdoc/html/render/write_shared.rs600
7 files changed, 7031 insertions, 0 deletions
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
new file mode 100644
index 000000000..2ed7a6f1b
--- /dev/null
+++ b/src/librustdoc/html/render/context.rs
@@ -0,0 +1,762 @@
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::rc::Rc;
+use std::sync::mpsc::{channel, Receiver};
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+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::{sym, Symbol};
+
+use super::print_item::{full_path, item_path, print_item};
+use super::search_index::build_index;
+use super::write_shared::write_shared;
+use super::{
+ collect_spans_and_sources, print_sidebar, scrape_examples_help, AllTypes, LinkFromSrc, NameDoc,
+ StylePath, BASIC_KEYWORDS,
+};
+
+use crate::clean::{self, types::ExternalLocation, ExternalCrate};
+use crate::config::{ModuleSorting, 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::{join_with_double_colon, Buffer};
+use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
+use crate::html::{layout, sources};
+use crate::scrape_examples::AllCallLocations;
+use crate::try_err;
+
+/// 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).
+pub(crate) struct Context<'tcx> {
+ /// Current hierarchy of components leading down to what's currently being
+ /// rendered
+ pub(crate) current: Vec<Symbol>,
+ /// The current destination folder of where HTML artifacts should be placed.
+ /// This changes as the context descends into the module hierarchy.
+ pub(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.
+ pub(super) render_redirect_pages: bool,
+ /// Tracks section IDs for `Deref` targets so they match in both the main
+ /// body and the sidebar.
+ pub(super) deref_id_map: FxHashMap<DefId, String>,
+ /// The map used to ensure all generated 'id=' attributes are unique.
+ pub(super) id_map: IdMap,
+ /// Shared mutable state.
+ ///
+ /// Issue for improving the situation: [#82381][]
+ ///
+ /// [#82381]: https://github.com/rust-lang/rust/issues/82381
+ pub(crate) shared: Rc<SharedContext<'tcx>>,
+ /// This flag indicates whether source links should be generated or not. If
+ /// the source files are present in the html rendering, then this will be
+ /// `true`.
+ pub(crate) include_sources: bool,
+}
+
+// `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+rustc_data_structures::static_assert_size!(Context<'_>, 128);
+
+/// Shared mutable state used in [`Context`] and elsewhere.
+pub(crate) struct SharedContext<'tcx> {
+ pub(crate) tcx: TyCtxt<'tcx>,
+ /// The path to the crate root source minus the file name.
+ /// Used for simplifying paths to the highlighted source code files.
+ pub(crate) src_root: PathBuf,
+ /// This describes the layout of each page, and is not modified after
+ /// creation of the context (contains info like the favicon and added html).
+ pub(crate) layout: layout::Layout,
+ /// The local file sources we've emitted and their respective url-paths.
+ pub(crate) local_sources: FxHashMap<PathBuf, String>,
+ /// Show the memory layout of types in the docs.
+ pub(super) show_type_layout: bool,
+ /// The base-URL of the issue tracker for when an item has been tagged with
+ /// an issue number.
+ pub(super) issue_tracker_base_url: Option<String>,
+ /// The directories that have already been created in this doc run. Used to reduce the number
+ /// of spurious `create_dir_all` calls.
+ created_dirs: RefCell<FxHashSet<PathBuf>>,
+ /// This flag indicates whether listings of modules (in the side bar and documentation itself)
+ /// should be ordered alphabetically or in order of appearance (in the source code).
+ pub(super) module_sorting: ModuleSorting,
+ /// Additional CSS files to be added to the generated docs.
+ pub(crate) style_files: Vec<StylePath>,
+ /// Suffix to be added on resource files (if suffix is "-v2" then "light.css" becomes
+ /// "light-v2.css").
+ pub(crate) resource_suffix: String,
+ /// Optional path string to be used to load static files on output pages. If not set, uses
+ /// combinations of `../` to reach the documentation root.
+ pub(crate) static_root_path: Option<String>,
+ /// The fs handle we are working with.
+ pub(crate) fs: DocFS,
+ pub(super) codes: ErrorCodes,
+ pub(super) playground: Option<markdown::Playground>,
+ all: RefCell<AllTypes>,
+ /// Storage for the errors produced while generating documentation so they
+ /// can be printed together at the end.
+ errors: Receiver<String>,
+ /// `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.
+ redirections: Option<RefCell<FxHashMap<String, String>>>,
+
+ /// Correspondance map used to link types used in the source code pages to allow to click on
+ /// links to jump to the type's definition.
+ pub(crate) span_correspondance_map: FxHashMap<rustc_span::Span, LinkFromSrc>,
+ /// The [`Cache`] used during rendering.
+ pub(crate) cache: Cache,
+
+ pub(crate) call_locations: AllCallLocations,
+}
+
+impl SharedContext<'_> {
+ pub(crate) fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
+ let mut dirs = self.created_dirs.borrow_mut();
+ if !dirs.contains(dst) {
+ try_err!(self.fs.create_dir_all(dst), dst);
+ dirs.insert(dst.to_path_buf());
+ }
+
+ Ok(())
+ }
+
+ pub(crate) fn edition(&self) -> Edition {
+ self.tcx.sess.edition()
+ }
+}
+
+impl<'tcx> Context<'tcx> {
+ pub(crate) fn tcx(&self) -> TyCtxt<'tcx> {
+ self.shared.tcx
+ }
+
+ pub(crate) fn cache(&self) -> &Cache {
+ &self.shared.cache
+ }
+
+ pub(super) fn sess(&self) -> &'tcx Session {
+ self.shared.tcx.sess
+ }
+
+ pub(super) fn derive_id(&mut self, id: String) -> String {
+ self.id_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(&mut self, it: &clean::Item, is_module: bool) -> String {
+ let mut title = String::new();
+ if !is_module {
+ title.push_str(it.name.unwrap().as_str());
+ }
+ if !it.is_primitive() && !it.is_keyword() {
+ if !is_module {
+ title.push_str(" in ");
+ }
+ // No need to include the namespace for primitive types and keywords
+ title.push_str(&join_with_double_colon(&self.current));
+ };
+ 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 name;
+ let tyname_s = if it.is_crate() {
+ name = format!("{} crate", tyname);
+ name.as_str()
+ } else {
+ tyname.as_str()
+ };
+
+ if !self.render_redirect_pages {
+ let clone_shared = Rc::clone(&self.shared);
+ let page = layout::Page {
+ css_class: tyname_s,
+ root_path: &self.root_path(),
+ static_root_path: clone_shared.static_root_path.as_deref(),
+ title: &title,
+ description: &desc,
+ keywords: &keywords,
+ resource_suffix: &clone_shared.resource_suffix,
+ };
+ let mut page_buffer = Buffer::html();
+ print_item(self, it, &mut page_buffer, &page);
+ layout::render(
+ &clone_shared.layout,
+ &page,
+ |buf: &mut _| print_sidebar(self, it, buf),
+ move |buf: &mut Buffer| buf.push_buffer(page_buffer),
+ &clone_shared.style_files,
+ )
+ } else {
+ if let Some(&(ref names, ty)) = self.cache().paths.get(&it.item_id.expect_def_id()) {
+ if self.current.len() + 1 != names.len()
+ || self.current.iter().zip(names.iter()).any(|(a, b)| a != b)
+ {
+ // We checked that the redirection isn't pointing to the current file,
+ // preventing an infinite redirection loop in the generated
+ // documentation.
+
+ let mut path = String::new();
+ for name in &names[..names.len() - 1] {
+ path.push_str(name.as_str());
+ path.push('/');
+ }
+ path.push_str(&item_path(ty, names.last().unwrap().as_str()));
+ match self.shared.redirections {
+ Some(ref redirections) => {
+ let mut current_path = String::new();
+ for name in &self.current {
+ current_path.push_str(name.as_str());
+ current_path.push('/');
+ }
+ current_path.push_str(&item_path(ty, names.last().unwrap().as_str()));
+ 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<String, Vec<NameDoc>> {
+ // BTreeMap instead of HashMap to get a sorted output
+ let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
+ let mut inserted: FxHashMap<ItemType, FxHashSet<Symbol>> = FxHashMap::default();
+
+ for item in &m.items {
+ if item.is_stripped() {
+ continue;
+ }
+
+ let short = item.type_();
+ let myname = match item.name {
+ None => continue,
+ Some(s) => s,
+ };
+ if inserted.entry(short).or_default().insert(myname) {
+ let short = short.to_string();
+ let myname = myname.to_string();
+ map.entry(short).or_default().push((
+ myname,
+ Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
+ ));
+ }
+ }
+
+ match self.shared.module_sorting {
+ ModuleSorting::Alphabetical => {
+ for items in map.values_mut() {
+ items.sort();
+ }
+ }
+ ModuleSorting::DeclarationOrder => {}
+ }
+ 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<String> {
+ self.href_from_span(item.span(self.tcx()), true)
+ }
+
+ pub(crate) fn href_from_span(&self, span: clean::Span, with_lines: bool) -> Option<String> {
+ if span.is_dummy() {
+ return None;
+ }
+ let mut root = self.root_path();
+ let mut path = String::new();
+ let cnum = span.cnum(self.sess());
+
+ // We can safely ignore synthetic `SourceFile`s.
+ let file = match span.filename(self.sess()) {
+ FileName::Real(ref path) => path.local_path_if_available().to_path_buf(),
+ _ => return None,
+ };
+ let file = &file;
+
+ let krate_sym;
+ 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)? {
+ ExternalLocation::Local => {
+ let e = ExternalCrate { crate_num: cnum };
+ (e.name(self.tcx()), e.src_root(self.tcx()))
+ }
+ ExternalLocation::Remote(ref s) => {
+ root = s.to_string();
+ let e = ExternalCrate { crate_num: cnum };
+ (e.name(self.tcx()), e.src_root(self.tcx()))
+ }
+ ExternalLocation::Unknown => return None,
+ };
+
+ sources::clean_path(&src_root, file, false, |component| {
+ path.push_str(&component.to_string_lossy());
+ path.push('/');
+ });
+ let mut fname = file.file_name().expect("source has no filename").to_os_string();
+ fname.push(".html");
+ path.push_str(&fname.to_string_lossy());
+ krate_sym = krate;
+ (krate_sym.as_str(), &path)
+ };
+
+ let anchor = if with_lines {
+ let loline = span.lo(self.sess()).line;
+ let hiline = span.hi(self.sess()).line;
+ format!(
+ "#{}",
+ if loline == hiline {
+ loline.to_string()
+ } else {
+ format!("{}-{}", loline, hiline)
+ }
+ )
+ } else {
+ "".to_string()
+ };
+ Some(format!(
+ "{root}src/{krate}/{path}{anchor}",
+ root = Escape(&root),
+ krate = krate,
+ path = path,
+ anchor = anchor
+ ))
+ }
+}
+
+/// Generates the documentation for `crate` into the directory `dst`
+impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
+ fn descr() -> &'static str {
+ "html"
+ }
+
+ const RUN_ON_MODULE: bool = true;
+
+ fn init(
+ krate: clean::Crate,
+ options: RenderOptions,
+ 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 emit_crate = options.should_emit_crate();
+ let RenderOptions {
+ output,
+ external_html,
+ id_map,
+ playground_url,
+ module_sorting,
+ themes: style_files,
+ default_settings,
+ extension_css,
+ resource_suffix,
+ static_root_path,
+ unstable_features,
+ generate_redirect_map,
+ show_type_layout,
+ generate_link_to_definition,
+ call_locations,
+ no_emit_shared,
+ ..
+ } = options;
+
+ let src_root = match krate.src(tcx) {
+ FileName::Real(ref p) => match p.local_path_if_available().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(tcx).to_string()), url });
+ }
+ let mut layout = layout::Layout {
+ logo: String::new(),
+ favicon: String::new(),
+ external_html,
+ default_settings,
+ krate: krate.name(tcx).to_string(),
+ css_file_extension: extension_css,
+ scrape_examples_extension: !call_locations.is_empty(),
+ };
+ 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
+ for attr in krate.module.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(tcx).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 (local_sources, matches) = collect_spans_and_sources(
+ tcx,
+ &krate,
+ &src_root,
+ include_sources,
+ generate_link_to_definition,
+ );
+
+ let (sender, receiver) = channel();
+ let mut scx = SharedContext {
+ tcx,
+ src_root,
+ local_sources,
+ issue_tracker_base_url,
+ layout,
+ created_dirs: Default::default(),
+ module_sorting,
+ style_files,
+ resource_suffix,
+ static_root_path,
+ fs: DocFS::new(sender),
+ codes: ErrorCodes::from(unstable_features.is_nightly_build()),
+ playground,
+ all: RefCell::new(AllTypes::new()),
+ errors: receiver,
+ redirections: if generate_redirect_map { Some(Default::default()) } else { None },
+ show_type_layout,
+ span_correspondance_map: matches,
+ cache,
+ call_locations,
+ };
+
+ // 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") });
+ scx.style_files.push(StylePath { path: PathBuf::from("dark.css") });
+ scx.style_files.push(StylePath { path: PathBuf::from("ayu.css") });
+
+ let dst = output;
+ scx.ensure_dir(&dst)?;
+
+ let mut cx = Context {
+ current: Vec::new(),
+ dst,
+ render_redirect_pages: false,
+ id_map,
+ deref_id_map: FxHashMap::default(),
+ shared: Rc::new(scx),
+ include_sources,
+ };
+
+ if emit_crate {
+ sources::render(&mut cx, &krate)?;
+ }
+
+ if !no_emit_shared {
+ // Build our search index
+ let index = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
+
+ // Write shared runs within a flock; disable thread dispatching of IO temporarily.
+ Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
+ write_shared(&mut cx, &krate, index, &md_opts)?;
+ Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
+ }
+
+ Ok((cx, krate))
+ }
+
+ fn make_child_renderer(&self) -> Self {
+ Self {
+ current: self.current.clone(),
+ dst: self.dst.clone(),
+ render_redirect_pages: self.render_redirect_pages,
+ deref_id_map: FxHashMap::default(),
+ id_map: IdMap::new(),
+ shared: Rc::clone(&self.shared),
+ include_sources: self.include_sources,
+ }
+ }
+
+ fn after_krate(&mut self) -> Result<(), Error> {
+ let crate_name = self.tcx().crate_name(LOCAL_CRATE);
+ let final_file = self.dst.join(crate_name.as_str()).join("all.html");
+ let settings_file = self.dst.join("settings.html");
+ let scrape_examples_help_file = self.dst.join("scrape-examples-help.html");
+
+ let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
+ if !root_path.ends_with('/') {
+ root_path.push('/');
+ }
+ let shared = Rc::clone(&self.shared);
+ let mut page = layout::Page {
+ title: "List of all items in this crate",
+ css_class: "mod",
+ root_path: "../",
+ static_root_path: shared.static_root_path.as_deref(),
+ description: "List of all items in this crate",
+ keywords: BASIC_KEYWORDS,
+ resource_suffix: &shared.resource_suffix,
+ };
+ let sidebar = if shared.cache.crate_version.is_some() {
+ format!("<h2 class=\"location\">Crate {}</h2>", crate_name)
+ } else {
+ String::new()
+ };
+ let all = shared.all.replace(AllTypes::new());
+ let v = layout::render(
+ &shared.layout,
+ &page,
+ sidebar,
+ |buf: &mut Buffer| all.print(buf),
+ &shared.style_files,
+ );
+ shared.fs.write(final_file, v)?;
+
+ // Generating settings page.
+ page.title = "Rustdoc settings";
+ page.description = "Settings of Rustdoc";
+ page.root_path = "./";
+
+ let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
+ let v = layout::render(
+ &shared.layout,
+ &page,
+ sidebar,
+ |buf: &mut Buffer| {
+ write!(
+ buf,
+ "<div class=\"main-heading\">\
+ <h1 class=\"fqn\">\
+ <span class=\"in-band\">Rustdoc settings</span>\
+ </h1>\
+ <span class=\"out-of-band\">\
+ <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
+ Back\
+ </a>\
+ </span>\
+ </div>\
+ <noscript>\
+ <section>\
+ You need to enable Javascript be able to update your settings.\
+ </section>\
+ </noscript>\
+ <link rel=\"stylesheet\" type=\"text/css\" \
+ href=\"{root_path}settings{suffix}.css\">\
+ <script defer src=\"{root_path}settings{suffix}.js\"></script>",
+ root_path = page.static_root_path.unwrap_or(""),
+ suffix = page.resource_suffix,
+ )
+ },
+ &shared.style_files,
+ );
+ shared.fs.write(settings_file, v)?;
+
+ if shared.layout.scrape_examples_extension {
+ page.title = "About scraped examples";
+ page.description = "How the scraped examples feature works in Rustdoc";
+ let v = layout::render(
+ &shared.layout,
+ &page,
+ "",
+ scrape_examples_help(&*shared),
+ &shared.style_files,
+ );
+ shared.fs.write(scrape_examples_help_file, v)?;
+ }
+
+ if let Some(ref redirections) = shared.redirections {
+ if !redirections.borrow().is_empty() {
+ let redirect_map_path =
+ self.dst.join(crate_name.as_str()).join("redirect-map.json");
+ let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
+ shared.ensure_dir(&self.dst.join(crate_name.as_str()))?;
+ shared.fs.write(redirect_map_path, paths)?;
+ }
+ }
+
+ // No need for it anymore.
+ drop(shared);
+
+ // Flush pending errors.
+ Rc::get_mut(&mut self.shared).unwrap().fs.close();
+ let nb_errors =
+ self.shared.errors.iter().map(|err| self.tcx().sess.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) -> 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 item_name = item.name.unwrap();
+ self.dst.push(&*item_name.as_str());
+ self.current.push(item_name);
+
+ info!("Recursing into {}", self.dst.display());
+
+ let buf = self.render_item(item, true);
+ // 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");
+ self.shared.fs.write(joint_dst, buf)?;
+ }
+
+ // Render sidebar-items.js used throughout this module.
+ if !self.render_redirect_pages {
+ let (clean::StrippedItem(box clean::ModuleItem(ref module)) | clean::ModuleItem(ref module)) = *item.kind
+ else { unreachable!() };
+ let items = self.build_sidebar_items(module);
+ let js_dst = self.dst.join(&format!("sidebar-items{}.js", self.shared.resource_suffix));
+ let v = format!("window.SIDEBAR_ITEMS = {};", serde_json::to_string(&items).unwrap());
+ self.shared.fs.write(js_dst, v)?;
+ }
+ Ok(())
+ }
+
+ fn mod_item_out(&mut self) -> Result<(), Error> {
+ info!("Recursed; leaving {}", self.dst.display());
+
+ // Go back to where we were at
+ self.dst.pop();
+ self.current.pop();
+ Ok(())
+ }
+
+ fn item(&mut self, item: clean::Item) -> Result<(), Error> {
+ // Stripped modules survive the rustdoc passes (i.e., `strip-private`)
+ // if they contain impls for public types. These modules can also
+ // contain items such as publicly re-exported structures.
+ //
+ // External crates will provide links to these structures, so
+ // these modules are recursed into, but not rendered normally
+ // (a flag on the context).
+ if !self.render_redirect_pages {
+ self.render_redirect_pages = item.is_stripped();
+ }
+
+ let buf = self.render_item(&item, false);
+ // 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)?;
+
+ if !self.render_redirect_pages {
+ self.shared.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.shared.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)?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn cache(&self) -> &Cache {
+ &self.shared.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
new file mode 100644
index 000000000..a262c8f7d
--- /dev/null
+++ b/src/librustdoc/html/render/mod.rs
@@ -0,0 +1,2849 @@
+//! Rustdoc's HTML rendering module.
+//!
+//! This modules contains the bulk of the logic necessary for rendering a
+//! rustdoc `clean::Crate` instance to a set of static HTML pages. This
+//! rendering process is largely driven by the `format!` syntax extension to
+//! perform all I/O into files and streams.
+//!
+//! The rendering process is largely driven by the `Context` and `Cache`
+//! structures. The cache is pre-populated by crawling the crate in question,
+//! and then it is shared among the various rendering threads. The cache is meant
+//! to be a fairly large structure not implementing `Clone` (because it's shared
+//! among threads). The context, however, should be a lightweight structure. This
+//! is cloned per-thread and contains information about what is currently being
+//! rendered.
+//!
+//! In order to speed up rendering (mostly because of markdown rendering), the
+//! rendering process has been parallelized. This parallelization is only
+//! exposed through the `crate` method on the context, and then also from the
+//! fact that the shared cache is stored in TLS (and must be accessed as such).
+//!
+//! In addition to rendering the crate itself, this module is also responsible
+//! for creating the corresponding search index and source file renderings.
+//! These threads are not parallelized (they haven't been a bottleneck yet), and
+//! both occur before the crate is rendered.
+
+pub(crate) mod search_index;
+
+#[cfg(test)]
+mod tests;
+
+mod context;
+mod print_item;
+mod span_map;
+mod write_shared;
+
+pub(crate) use self::context::*;
+pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc};
+
+use std::collections::VecDeque;
+use std::default::Default;
+use std::fmt;
+use std::fs;
+use std::iter::Peekable;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::str;
+use std::string::ToString;
+
+use rustc_ast_pretty::pprust;
+use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def::CtorKind;
+use rustc_hir::def_id::DefId;
+use rustc_hir::Mutability;
+use rustc_middle::middle::stability;
+use rustc_middle::ty;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::{
+ symbol::{sym, Symbol},
+ BytePos, FileName, RealFileName,
+};
+use serde::ser::SerializeSeq;
+use serde::{Serialize, Serializer};
+
+use crate::clean::{self, ItemId, RenderedLink, SelfTy};
+use crate::error::Error;
+use crate::formats::cache::Cache;
+use crate::formats::item_type::ItemType;
+use crate::formats::{AssocItemRender, Impl, RenderMode};
+use crate::html::escape::Escape;
+use crate::html::format::{
+ href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
+ print_default_space, print_generic_bounds, print_where_clause, Buffer, Ending, HrefError,
+ PrintWithSpace,
+};
+use crate::html::highlight;
+use crate::html::markdown::{HeadingOffset, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine};
+use crate::html::sources;
+use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
+use crate::scrape_examples::{CallData, CallLocation};
+use crate::try_none;
+use crate::DOC_RUST_LANG_ORG_CHANNEL;
+
+/// A pair of name and its optional document.
+pub(crate) type NameDoc = (String, Option<String>);
+
+pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ {
+ crate::html::format::display_fn(move |f| {
+ if !v.ends_with('/') && !v.is_empty() { write!(f, "{}/", v) } else { f.write_str(v) }
+ })
+}
+
+// Helper structs for rendering items/sidebars and carrying along contextual
+// information
+
+/// Struct representing one entry in the JS search index. These are all emitted
+/// by hand to a large JS file at the end of cache-creation.
+#[derive(Debug)]
+pub(crate) struct IndexItem {
+ pub(crate) ty: ItemType,
+ pub(crate) name: String,
+ pub(crate) path: String,
+ pub(crate) desc: String,
+ pub(crate) parent: Option<DefId>,
+ pub(crate) parent_idx: Option<usize>,
+ pub(crate) search_type: Option<IndexItemFunctionType>,
+ pub(crate) aliases: Box<[Symbol]>,
+}
+
+/// A type used for the search index.
+#[derive(Debug)]
+pub(crate) struct RenderType {
+ id: Option<RenderTypeId>,
+ generics: Option<Vec<RenderType>>,
+}
+
+impl Serialize for RenderType {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let id = match &self.id {
+ // 0 is a sentinel, everything else is one-indexed
+ None => 0,
+ Some(RenderTypeId::Index(idx)) => idx + 1,
+ _ => panic!("must convert render types to indexes before serializing"),
+ };
+ if let Some(generics) = &self.generics {
+ let mut seq = serializer.serialize_seq(None)?;
+ seq.serialize_element(&id)?;
+ seq.serialize_element(generics)?;
+ seq.end()
+ } else {
+ id.serialize(serializer)
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum RenderTypeId {
+ DefId(DefId),
+ Primitive(clean::PrimitiveType),
+ Index(usize),
+}
+
+/// Full type of functions/methods in the search index.
+#[derive(Debug)]
+pub(crate) struct IndexItemFunctionType {
+ inputs: Vec<RenderType>,
+ output: Vec<RenderType>,
+}
+
+impl Serialize for IndexItemFunctionType {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ // If we couldn't figure out a type, just write `0`.
+ let has_missing = self
+ .inputs
+ .iter()
+ .chain(self.output.iter())
+ .any(|i| i.id.is_none() && i.generics.is_none());
+ if has_missing {
+ 0.serialize(serializer)
+ } else {
+ let mut seq = serializer.serialize_seq(None)?;
+ match &self.inputs[..] {
+ [one] if one.generics.is_none() => seq.serialize_element(one)?,
+ _ => seq.serialize_element(&self.inputs)?,
+ }
+ match &self.output[..] {
+ [] => {}
+ [one] if one.generics.is_none() => seq.serialize_element(one)?,
+ _ => seq.serialize_element(&self.output)?,
+ }
+ seq.end()
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct StylePath {
+ /// The path to the theme
+ pub(crate) path: PathBuf,
+}
+
+impl StylePath {
+ pub(crate) fn basename(&self) -> Result<String, Error> {
+ Ok(try_none!(try_none!(self.path.file_stem(), &self.path).to_str(), &self.path).to_string())
+ }
+}
+
+fn write_srclink(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) {
+ if let Some(l) = cx.src_href(item) {
+ write!(buf, "<a class=\"srclink\" href=\"{}\">source</a>", l)
+ }
+}
+
+#[derive(Debug, Eq, PartialEq, Hash)]
+struct ItemEntry {
+ url: String,
+ name: String,
+}
+
+impl ItemEntry {
+ fn new(mut url: String, name: String) -> ItemEntry {
+ while url.starts_with('/') {
+ url.remove(0);
+ }
+ ItemEntry { url, name }
+ }
+}
+
+impl ItemEntry {
+ pub(crate) fn print(&self) -> impl fmt::Display + '_ {
+ crate::html::format::display_fn(move |f| {
+ write!(f, "<a href=\"{}\">{}</a>", self.url, Escape(&self.name))
+ })
+ }
+}
+
+impl PartialOrd for ItemEntry {
+ fn partial_cmp(&self, other: &ItemEntry) -> Option<::std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for ItemEntry {
+ fn cmp(&self, other: &ItemEntry) -> ::std::cmp::Ordering {
+ self.name.cmp(&other.name)
+ }
+}
+
+#[derive(Debug)]
+struct AllTypes {
+ structs: FxHashSet<ItemEntry>,
+ enums: FxHashSet<ItemEntry>,
+ unions: FxHashSet<ItemEntry>,
+ primitives: FxHashSet<ItemEntry>,
+ traits: FxHashSet<ItemEntry>,
+ macros: FxHashSet<ItemEntry>,
+ functions: FxHashSet<ItemEntry>,
+ typedefs: FxHashSet<ItemEntry>,
+ opaque_tys: FxHashSet<ItemEntry>,
+ statics: FxHashSet<ItemEntry>,
+ constants: FxHashSet<ItemEntry>,
+ attributes: FxHashSet<ItemEntry>,
+ derives: FxHashSet<ItemEntry>,
+ trait_aliases: FxHashSet<ItemEntry>,
+}
+
+impl AllTypes {
+ fn new() -> AllTypes {
+ let new_set = |cap| FxHashSet::with_capacity_and_hasher(cap, Default::default());
+ AllTypes {
+ structs: new_set(100),
+ enums: new_set(100),
+ unions: new_set(100),
+ primitives: new_set(26),
+ traits: new_set(100),
+ macros: new_set(100),
+ functions: new_set(100),
+ typedefs: new_set(100),
+ opaque_tys: new_set(100),
+ statics: new_set(100),
+ constants: new_set(100),
+ attributes: new_set(100),
+ derives: new_set(100),
+ trait_aliases: new_set(100),
+ }
+ }
+
+ fn append(&mut self, item_name: String, item_type: &ItemType) {
+ let mut url: Vec<_> = item_name.split("::").skip(1).collect();
+ if let Some(name) = url.pop() {
+ let new_url = format!("{}/{}.{}.html", url.join("/"), item_type, name);
+ url.push(name);
+ let name = url.join("::");
+ match *item_type {
+ ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)),
+ ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)),
+ ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)),
+ ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)),
+ ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)),
+ ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)),
+ ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)),
+ ItemType::Typedef => self.typedefs.insert(ItemEntry::new(new_url, name)),
+ ItemType::OpaqueTy => self.opaque_tys.insert(ItemEntry::new(new_url, name)),
+ ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)),
+ ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)),
+ ItemType::ProcAttribute => self.attributes.insert(ItemEntry::new(new_url, name)),
+ ItemType::ProcDerive => self.derives.insert(ItemEntry::new(new_url, name)),
+ ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)),
+ _ => true,
+ };
+ }
+ }
+}
+
+impl AllTypes {
+ fn print(self, f: &mut Buffer) {
+ fn print_entries(f: &mut Buffer, e: &FxHashSet<ItemEntry>, title: &str, class: &str) {
+ if !e.is_empty() {
+ let mut e: Vec<&ItemEntry> = e.iter().collect();
+ e.sort();
+ write!(
+ f,
+ "<h3 id=\"{}\">{}</h3><ul class=\"{} docblock\">",
+ title.replace(' ', "-"), // IDs cannot contain whitespaces.
+ title,
+ class
+ );
+
+ for s in e.iter() {
+ write!(f, "<li>{}</li>", s.print());
+ }
+
+ f.write_str("</ul>");
+ }
+ }
+
+ f.write_str(
+ "<h1 class=\"fqn\">\
+ <span class=\"in-band\">List of all items</span>\
+ </h1>",
+ );
+ // Note: print_entries does not escape the title, because we know the current set of titles
+ // doesn't require escaping.
+ print_entries(f, &self.structs, "Structs", "structs");
+ print_entries(f, &self.enums, "Enums", "enums");
+ print_entries(f, &self.unions, "Unions", "unions");
+ print_entries(f, &self.primitives, "Primitives", "primitives");
+ print_entries(f, &self.traits, "Traits", "traits");
+ print_entries(f, &self.macros, "Macros", "macros");
+ print_entries(f, &self.attributes, "Attribute Macros", "attributes");
+ print_entries(f, &self.derives, "Derive Macros", "derives");
+ print_entries(f, &self.functions, "Functions", "functions");
+ print_entries(f, &self.typedefs, "Typedefs", "typedefs");
+ print_entries(f, &self.trait_aliases, "Trait Aliases", "trait-aliases");
+ print_entries(f, &self.opaque_tys, "Opaque Types", "opaque-types");
+ print_entries(f, &self.statics, "Statics", "statics");
+ print_entries(f, &self.constants, "Constants", "constants")
+ }
+}
+
+fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
+ let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned();
+ content.push_str(&format!(
+ "## More information\n\n\
+ If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).",
+ DOC_RUST_LANG_ORG_CHANNEL));
+
+ let mut ids = IdMap::default();
+ format!(
+ "<div class=\"main-heading\">\
+ <h1 class=\"fqn\">\
+ <span class=\"in-band\">About scraped examples</span>\
+ </h1>\
+ </div>\
+ <div>{}</div>",
+ Markdown {
+ content: &content,
+ links: &[],
+ ids: &mut ids,
+ error_codes: shared.codes,
+ edition: shared.edition(),
+ playground: &shared.playground,
+ heading_offset: HeadingOffset::H1
+ }
+ .into_string()
+ )
+}
+
+fn document(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ item: &clean::Item,
+ parent: Option<&clean::Item>,
+ heading_offset: HeadingOffset,
+) {
+ if let Some(ref name) = item.name {
+ info!("Documenting {}", name);
+ }
+ document_item_info(w, cx, item, parent);
+ if parent.is_none() {
+ document_full_collapsible(w, item, cx, heading_offset);
+ } else {
+ document_full(w, item, cx, heading_offset);
+ }
+}
+
+/// Render md_text as markdown.
+fn render_markdown(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ md_text: &str,
+ links: Vec<RenderedLink>,
+ heading_offset: HeadingOffset,
+) {
+ write!(
+ w,
+ "<div class=\"docblock\">{}</div>",
+ Markdown {
+ content: md_text,
+ links: &links,
+ ids: &mut cx.id_map,
+ error_codes: cx.shared.codes,
+ edition: cx.shared.edition(),
+ playground: &cx.shared.playground,
+ heading_offset,
+ }
+ .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: &mut Context<'_>,
+ link: AssocItemLink<'_>,
+ parent: &clean::Item,
+ show_def_docs: bool,
+) {
+ document_item_info(w, cx, item, Some(parent));
+ if !show_def_docs {
+ return;
+ }
+ if let Some(s) = item.doc_value() {
+ let mut summary_html = MarkdownSummaryLine(&s, &item.links(cx)).into_string();
+
+ if s.contains('\n') {
+ let link = format!(r#" <a{}>Read more</a>"#, assoc_href_attr(item, link, cx));
+
+ if let Some(idx) = summary_html.rfind("</p>") {
+ summary_html.insert_str(idx, &link);
+ } else {
+ summary_html.push_str(&link);
+ }
+ }
+
+ write!(w, "<div class='docblock'>{}</div>", summary_html,);
+ }
+}
+
+fn document_full_collapsible(
+ w: &mut Buffer,
+ item: &clean::Item,
+ cx: &mut Context<'_>,
+ heading_offset: HeadingOffset,
+) {
+ document_full_inner(w, item, cx, true, heading_offset);
+}
+
+fn document_full(
+ w: &mut Buffer,
+ item: &clean::Item,
+ cx: &mut Context<'_>,
+ heading_offset: HeadingOffset,
+) {
+ document_full_inner(w, item, cx, false, heading_offset);
+}
+
+fn document_full_inner(
+ w: &mut Buffer,
+ item: &clean::Item,
+ cx: &mut Context<'_>,
+ is_collapsible: bool,
+ heading_offset: HeadingOffset,
+) {
+ if let Some(s) = item.collapsed_doc_value() {
+ debug!("Doc block: =====\n{}\n=====", s);
+ if is_collapsible {
+ w.write_str(
+ "<details class=\"rustdoc-toggle top-doc\" open>\
+ <summary class=\"hideme\">\
+ <span>Expand description</span>\
+ </summary>",
+ );
+ render_markdown(w, cx, &s, item.links(cx), heading_offset);
+ w.write_str("</details>");
+ } else {
+ render_markdown(w, cx, &s, item.links(cx), heading_offset);
+ }
+ }
+
+ let kind = match &*item.kind {
+ clean::ItemKind::StrippedItem(box kind) | kind => kind,
+ };
+
+ if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
+ render_call_locations(w, cx, item);
+ }
+}
+
+/// Add extra information about an item such as:
+///
+/// * Stability
+/// * Deprecated
+/// * Required features (through the `doc_cfg` feature)
+fn document_item_info(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ item: &clean::Item,
+ parent: Option<&clean::Item>,
+) {
+ let item_infos = short_item_info(item, cx, parent);
+ if !item_infos.is_empty() {
+ w.write_str("<span class=\"item-info\">");
+ for info in item_infos {
+ w.write_str(&info);
+ }
+ w.write_str("</span>");
+ }
+}
+
+fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> {
+ let cfg = match (&item.cfg, parent.and_then(|p| p.cfg.as_ref())) {
+ (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
+ (cfg, _) => cfg.as_deref().cloned(),
+ };
+
+ debug!("Portability {:?} - {:?} = {:?}", item.cfg, parent.and_then(|p| p.cfg.as_ref()), cfg);
+
+ Some(format!("<div class=\"stab portability\">{}</div>", cfg?.render_long_html()))
+}
+
+/// Render the stability, deprecation and portability information that is displayed at the top of
+/// the item's documentation.
+fn short_item_info(
+ item: &clean::Item,
+ cx: &mut Context<'_>,
+ parent: Option<&clean::Item>,
+) -> Vec<String> {
+ let mut extra_info = vec![];
+ let error_codes = cx.shared.codes;
+
+ if let Some(depr @ Deprecation { note, since, is_since_rustc_version: _, suggestion: _ }) =
+ item.deprecation(cx.tcx())
+ {
+ // We display deprecation messages for #[deprecated], but only display
+ // the future-deprecation messages for rustc versions.
+ let mut message = if let Some(since) = since {
+ let since = since.as_str();
+ if !stability::deprecation_in_effect(&depr) {
+ if since == "TBD" {
+ String::from("Deprecating in a future Rust version")
+ } else {
+ format!("Deprecating in {}", Escape(since))
+ }
+ } else {
+ format!("Deprecated since {}", Escape(since))
+ }
+ } else {
+ String::from("Deprecated")
+ };
+
+ if let Some(note) = note {
+ let note = note.as_str();
+ let html = MarkdownHtml(
+ note,
+ &mut cx.id_map,
+ error_codes,
+ cx.shared.edition(),
+ &cx.shared.playground,
+ );
+ message.push_str(&format!(": {}", html.into_string()));
+ }
+ extra_info.push(format!(
+ "<div class=\"stab deprecated\"><span class=\"emoji\">👎</span> {}</div>",
+ message,
+ ));
+ }
+
+ // Render unstable items. But don't render "rustc_private" crates (internal compiler crates).
+ // Those crates are permanently unstable so it makes no sense to render "unstable" everywhere.
+ if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item
+ .stability(cx.tcx())
+ .as_ref()
+ .filter(|stab| stab.feature != sym::rustc_private)
+ .map(|stab| (stab.level, stab.feature))
+ {
+ let mut message =
+ "<span class=\"emoji\">🔬</span> This is a nightly-only experimental API.".to_owned();
+
+ let mut feature = format!("<code>{}</code>", Escape(feature.as_str()));
+ if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) {
+ feature.push_str(&format!(
+ "&nbsp;<a href=\"{url}{issue}\">#{issue}</a>",
+ url = url,
+ issue = issue
+ ));
+ }
+
+ message.push_str(&format!(" ({})", feature));
+
+ extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message));
+ }
+
+ if let Some(portability) = portability(item, parent) {
+ extra_info.push(portability);
+ }
+
+ extra_info
+}
+
+// Render the list of items inside one of the sections "Trait Implementations",
+// "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages).
+fn render_impls(
+ cx: &mut Context<'_>,
+ w: &mut Buffer,
+ impls: &[&&Impl],
+ containing_item: &clean::Item,
+ toggle_open_by_default: bool,
+) {
+ let tcx = cx.tcx();
+ let mut rendered_impls = impls
+ .iter()
+ .map(|i| {
+ let did = i.trait_did().unwrap();
+ let provided_trait_methods = i.inner_impl().provided_trait_methods(tcx);
+ let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods);
+ let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() };
+ render_impl(
+ &mut buffer,
+ cx,
+ i,
+ containing_item,
+ assoc_link,
+ RenderMode::Normal,
+ None,
+ &[],
+ ImplRenderingParameters {
+ show_def_docs: true,
+ show_default_items: true,
+ show_non_assoc_items: true,
+ toggle_open_by_default,
+ },
+ );
+ buffer.into_inner()
+ })
+ .collect::<Vec<_>>();
+ rendered_impls.sort();
+ w.write_str(&rendered_impls.join(""));
+}
+
+/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
+fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
+ let name = it.name.unwrap();
+ let item_type = it.type_();
+
+ let href = match link {
+ AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
+ AssocItemLink::Anchor(None) => Some(format!("#{}.{}", item_type, name)),
+ AssocItemLink::GotoSource(did, provided_methods) => {
+ // We're creating a link from the implementation of an associated item to its
+ // declaration in the trait declaration.
+ let item_type = match item_type {
+ // For historical but not technical reasons, the item type of methods in
+ // trait declarations depends on whether the method is required (`TyMethod`) or
+ // provided (`Method`).
+ ItemType::Method | ItemType::TyMethod => {
+ if provided_methods.contains(&name) {
+ ItemType::Method
+ } else {
+ ItemType::TyMethod
+ }
+ }
+ // For associated types and constants, no such distinction exists.
+ item_type => item_type,
+ };
+
+ match href(did.expect_def_id(), cx) {
+ Ok((url, ..)) => Some(format!("{}#{}.{}", url, item_type, name)),
+ // The link is broken since it points to an external crate that wasn't documented.
+ // Do not create any link in such case. This is better than falling back to a
+ // dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
+ // (that used to happen in older versions). Indeed, in most cases this dummy would
+ // coincide with the `id`. However, it would not always do so.
+ // In general, this dummy would be incorrect:
+ // If the type with the trait impl also had an inherent impl with an assoc. item of
+ // the *same* name as this impl item, the dummy would link to that one even though
+ // those two items are distinct!
+ // In this scenario, the actual `id` of this impl item would be
+ // `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
+ Err(HrefError::DocumentationNotBuilt) => None,
+ Err(_) => Some(format!("#{}.{}", item_type, name)),
+ }
+ }
+ };
+
+ // If there is no `href` for the reason explained above, simply do not render it which is valid:
+ // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
+ href.map(|href| format!(" href=\"{}\"", href)).unwrap_or_default()
+}
+
+fn assoc_const(
+ w: &mut Buffer,
+ it: &clean::Item,
+ ty: &clean::Type,
+ default: Option<&clean::ConstantKind>,
+ link: AssocItemLink<'_>,
+ extra: &str,
+ cx: &Context<'_>,
+) {
+ write!(
+ w,
+ "{extra}{vis}const <a{href} class=\"constant\">{name}</a>: {ty}",
+ extra = extra,
+ vis = it.visibility.print_with_space(it.item_id, cx),
+ href = assoc_href_attr(it, link, cx),
+ name = it.name.as_ref().unwrap(),
+ ty = ty.print(cx),
+ );
+ if let Some(default) = default {
+ write!(w, " = ");
+
+ // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the
+ // hood which adds noisy underscores and a type suffix to number literals.
+ // This hurts readability in this context especially when more complex expressions
+ // are involved and it doesn't add much of value.
+ // Find a way to print constants here without all that jazz.
+ write!(w, "{}", Escape(&default.value(cx.tcx()).unwrap_or_else(|| default.expr(cx.tcx()))));
+ }
+}
+
+fn assoc_type(
+ w: &mut Buffer,
+ it: &clean::Item,
+ generics: &clean::Generics,
+ bounds: &[clean::GenericBound],
+ default: Option<&clean::Type>,
+ link: AssocItemLink<'_>,
+ indent: usize,
+ cx: &Context<'_>,
+) {
+ write!(
+ w,
+ "{indent}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
+ indent = " ".repeat(indent),
+ href = assoc_href_attr(it, link, cx),
+ name = it.name.as_ref().unwrap(),
+ generics = generics.print(cx),
+ );
+ if !bounds.is_empty() {
+ write!(w, ": {}", print_generic_bounds(bounds, cx))
+ }
+ write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
+ if let Some(default) = default {
+ write!(w, " = {}", default.print(cx))
+ }
+}
+
+fn assoc_method(
+ w: &mut Buffer,
+ meth: &clean::Item,
+ g: &clean::Generics,
+ d: &clean::FnDecl,
+ link: AssocItemLink<'_>,
+ parent: ItemType,
+ cx: &Context<'_>,
+ render_mode: RenderMode,
+) {
+ let header = meth.fn_header(cx.tcx()).expect("Trying to get header from a non-function item");
+ let name = meth.name.as_ref().unwrap();
+ let vis = meth.visibility.print_with_space(meth.item_id, cx).to_string();
+ // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
+ // this condition.
+ let constness = match render_mode {
+ RenderMode::Normal => {
+ print_constness_with_space(&header.constness, meth.const_stability(cx.tcx()))
+ }
+ RenderMode::ForDeref { .. } => "",
+ };
+ let asyncness = header.asyncness.print_with_space();
+ let unsafety = header.unsafety.print_with_space();
+ let defaultness = print_default_space(meth.is_default());
+ let abi = print_abi_with_space(header.abi).to_string();
+ let href = assoc_href_attr(meth, link, cx);
+
+ // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
+ let generics_len = format!("{:#}", g.print(cx)).len();
+ let mut header_len = "fn ".len()
+ + vis.len()
+ + constness.len()
+ + asyncness.len()
+ + unsafety.len()
+ + defaultness.len()
+ + abi.len()
+ + name.as_str().len()
+ + generics_len;
+
+ let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
+ header_len += 4;
+ let indent_str = " ";
+ render_attributes_in_pre(w, meth, indent_str);
+ (4, indent_str, Ending::NoNewline)
+ } else {
+ render_attributes_in_code(w, meth);
+ (0, "", Ending::Newline)
+ };
+ w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
+ write!(
+ w,
+ "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a{href} class=\"fnname\">{name}</a>\
+ {generics}{decl}{notable_traits}{where_clause}",
+ indent = indent_str,
+ vis = vis,
+ constness = constness,
+ asyncness = asyncness,
+ unsafety = unsafety,
+ defaultness = defaultness,
+ abi = abi,
+ href = href,
+ name = name,
+ generics = g.print(cx),
+ decl = d.full_print(header_len, indent, header.asyncness, cx),
+ notable_traits = notable_traits_decl(d, cx),
+ where_clause = print_where_clause(g, cx, indent, end_newline),
+ )
+}
+
+/// Writes a span containing the versions at which an item became stable and/or const-stable. For
+/// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would
+/// write a span containing "1.0.0 (const: 1.45.0)".
+///
+/// Returns `true` if a stability annotation was rendered.
+///
+/// Stability and const-stability are considered separately. If the item is unstable, no version
+/// will be written. If the item is const-unstable, "const: unstable" will be appended to the
+/// span, with a link to the tracking issue if present. If an item's stability or const-stability
+/// version matches the version of its enclosing item, that version will be omitted.
+///
+/// Note that it is possible for an unstable function to be const-stable. In that case, the span
+/// will include the const-stable version, but no stable version will be emitted, as a natural
+/// consequence of the above rules.
+fn render_stability_since_raw(
+ w: &mut Buffer,
+ ver: Option<Symbol>,
+ const_stability: Option<ConstStability>,
+ containing_ver: Option<Symbol>,
+ containing_const_ver: Option<Symbol>,
+) -> bool {
+ let stable_version = ver.filter(|inner| !inner.is_empty() && Some(*inner) != containing_ver);
+
+ let mut title = String::new();
+ let mut stability = String::new();
+
+ if let Some(ver) = stable_version {
+ stability.push_str(ver.as_str());
+ title.push_str(&format!("Stable since Rust version {}", ver));
+ }
+
+ let const_title_and_stability = match const_stability {
+ Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. })
+ if Some(since) != containing_const_ver =>
+ {
+ Some((format!("const since {}", since), format!("const: {}", since)))
+ }
+ Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => {
+ let unstable = if let Some(n) = issue {
+ format!(
+ r#"<a href="https://github.com/rust-lang/rust/issues/{}" title="Tracking issue for {}">unstable</a>"#,
+ n, feature
+ )
+ } else {
+ String::from("unstable")
+ };
+
+ Some((String::from("const unstable"), format!("const: {}", unstable)))
+ }
+ _ => None,
+ };
+
+ if let Some((const_title, const_stability)) = const_title_and_stability {
+ if !title.is_empty() {
+ title.push_str(&format!(", {}", const_title));
+ } else {
+ title.push_str(&const_title);
+ }
+
+ if !stability.is_empty() {
+ stability.push_str(&format!(" ({})", const_stability));
+ } else {
+ stability.push_str(&const_stability);
+ }
+ }
+
+ if !stability.is_empty() {
+ write!(w, r#"<span class="since" title="{}">{}</span>"#, title, stability);
+ }
+
+ !stability.is_empty()
+}
+
+fn render_assoc_item(
+ w: &mut Buffer,
+ item: &clean::Item,
+ link: AssocItemLink<'_>,
+ parent: ItemType,
+ cx: &Context<'_>,
+ render_mode: RenderMode,
+) {
+ match &*item.kind {
+ clean::StrippedItem(..) => {}
+ clean::TyMethodItem(m) => {
+ assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
+ }
+ clean::MethodItem(m, _) => {
+ assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
+ }
+ kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => assoc_const(
+ w,
+ item,
+ ty,
+ match kind {
+ clean::TyAssocConstItem(_) => None,
+ clean::AssocConstItem(_, default) => Some(default),
+ _ => unreachable!(),
+ },
+ link,
+ if parent == ItemType::Trait { " " } else { "" },
+ cx,
+ ),
+ clean::TyAssocTypeItem(ref generics, ref bounds) => assoc_type(
+ w,
+ item,
+ generics,
+ bounds,
+ None,
+ link,
+ if parent == ItemType::Trait { 4 } else { 0 },
+ cx,
+ ),
+ clean::AssocTypeItem(ref ty, ref bounds) => assoc_type(
+ w,
+ item,
+ &ty.generics,
+ bounds,
+ Some(ty.item_type.as_ref().unwrap_or(&ty.type_)),
+ link,
+ if parent == ItemType::Trait { 4 } else { 0 },
+ cx,
+ ),
+ _ => panic!("render_assoc_item called on non-associated-item"),
+ }
+}
+
+const ALLOWED_ATTRIBUTES: &[Symbol] =
+ &[sym::export_name, sym::link_section, sym::no_mangle, sym::repr, sym::non_exhaustive];
+
+fn attributes(it: &clean::Item) -> Vec<String> {
+ it.attrs
+ .other_attrs
+ .iter()
+ .filter_map(|attr| {
+ if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) {
+ Some(
+ pprust::attribute_to_string(attr)
+ .replace("\\\n", "")
+ .replace('\n', "")
+ .replace(" ", " "),
+ )
+ } else {
+ None
+ }
+ })
+ .collect()
+}
+
+// When an attribute is rendered inside a `<pre>` tag, it is formatted using
+// a whitespace prefix and newline.
+fn render_attributes_in_pre(w: &mut Buffer, it: &clean::Item, prefix: &str) {
+ for a in attributes(it) {
+ writeln!(w, "{}{}", prefix, a);
+ }
+}
+
+// When an attribute is rendered inside a <code> tag, it is formatted using
+// a div to produce a newline after it.
+fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item) {
+ for a in attributes(it) {
+ write!(w, "<div class=\"code-attribute\">{}</div>", a);
+ }
+}
+
+#[derive(Copy, Clone)]
+enum AssocItemLink<'a> {
+ Anchor(Option<&'a str>),
+ GotoSource(ItemId, &'a FxHashSet<Symbol>),
+}
+
+impl<'a> AssocItemLink<'a> {
+ fn anchor(&self, id: &'a str) -> Self {
+ match *self {
+ AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(id)),
+ ref other => *other,
+ }
+ }
+}
+
+fn render_assoc_items(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ containing_item: &clean::Item,
+ it: DefId,
+ what: AssocItemRender<'_>,
+) {
+ let mut derefs = FxHashSet::default();
+ derefs.insert(it);
+ render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs)
+}
+
+fn render_assoc_items_inner(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ containing_item: &clean::Item,
+ it: DefId,
+ what: AssocItemRender<'_>,
+ derefs: &mut FxHashSet<DefId>,
+) {
+ info!("Documenting associated items of {:?}", containing_item.name);
+ let shared = Rc::clone(&cx.shared);
+ let cache = &shared.cache;
+ let Some(v) = cache.impls.get(&it) else { return };
+ let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
+ if !non_trait.is_empty() {
+ let mut tmp_buf = Buffer::empty_from(w);
+ let (render_mode, id) = match what {
+ AssocItemRender::All => {
+ tmp_buf.write_str(
+ "<h2 id=\"implementations\" class=\"small-section-header\">\
+ Implementations\
+ <a href=\"#implementations\" class=\"anchor\"></a>\
+ </h2>",
+ );
+ (RenderMode::Normal, "implementations-list".to_owned())
+ }
+ AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
+ let id =
+ cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
+ if let Some(def_id) = type_.def_id(cx.cache()) {
+ cx.deref_id_map.insert(def_id, id.clone());
+ }
+ write!(
+ tmp_buf,
+ "<h2 id=\"{id}\" class=\"small-section-header\">\
+ <span>Methods from {trait_}&lt;Target = {type_}&gt;</span>\
+ <a href=\"#{id}\" class=\"anchor\"></a>\
+ </h2>",
+ id = id,
+ trait_ = trait_.print(cx),
+ type_ = type_.print(cx),
+ );
+ (RenderMode::ForDeref { mut_: deref_mut_ }, cx.derive_id(id))
+ }
+ };
+ let mut impls_buf = Buffer::empty_from(w);
+ for i in &non_trait {
+ render_impl(
+ &mut impls_buf,
+ cx,
+ i,
+ containing_item,
+ AssocItemLink::Anchor(None),
+ render_mode,
+ None,
+ &[],
+ ImplRenderingParameters {
+ show_def_docs: true,
+ show_default_items: true,
+ show_non_assoc_items: true,
+ toggle_open_by_default: true,
+ },
+ );
+ }
+ if !impls_buf.is_empty() {
+ w.push_buffer(tmp_buf);
+ write!(w, "<div id=\"{}\">", id);
+ w.push_buffer(impls_buf);
+ w.write_str("</div>");
+ }
+ }
+
+ if !traits.is_empty() {
+ let deref_impl =
+ traits.iter().find(|t| t.trait_did() == cx.tcx().lang_items().deref_trait());
+ if let Some(impl_) = deref_impl {
+ let has_deref_mut =
+ traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
+ render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, derefs);
+ }
+
+ // If we were already one level into rendering deref methods, we don't want to render
+ // anything after recursing into any further deref methods above.
+ if let AssocItemRender::DerefFor { .. } = what {
+ return;
+ }
+
+ let (synthetic, concrete): (Vec<&&Impl>, Vec<&&Impl>) =
+ traits.iter().partition(|t| t.inner_impl().kind.is_auto());
+ let (blanket_impl, concrete): (Vec<&&Impl>, _) =
+ concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
+
+ let mut impls = Buffer::empty_from(w);
+ render_impls(cx, &mut impls, &concrete, containing_item, true);
+ let impls = impls.into_inner();
+ if !impls.is_empty() {
+ write!(
+ w,
+ "<h2 id=\"trait-implementations\" class=\"small-section-header\">\
+ Trait Implementations\
+ <a href=\"#trait-implementations\" class=\"anchor\"></a>\
+ </h2>\
+ <div id=\"trait-implementations-list\">{}</div>",
+ impls
+ );
+ }
+
+ if !synthetic.is_empty() {
+ w.write_str(
+ "<h2 id=\"synthetic-implementations\" class=\"small-section-header\">\
+ Auto Trait Implementations\
+ <a href=\"#synthetic-implementations\" class=\"anchor\"></a>\
+ </h2>\
+ <div id=\"synthetic-implementations-list\">",
+ );
+ render_impls(cx, w, &synthetic, containing_item, false);
+ w.write_str("</div>");
+ }
+
+ if !blanket_impl.is_empty() {
+ w.write_str(
+ "<h2 id=\"blanket-implementations\" class=\"small-section-header\">\
+ Blanket Implementations\
+ <a href=\"#blanket-implementations\" class=\"anchor\"></a>\
+ </h2>\
+ <div id=\"blanket-implementations-list\">",
+ );
+ render_impls(cx, w, &blanket_impl, containing_item, false);
+ w.write_str("</div>");
+ }
+ }
+}
+
+fn render_deref_methods(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ impl_: &Impl,
+ container_item: &clean::Item,
+ deref_mut: bool,
+ derefs: &mut FxHashSet<DefId>,
+) {
+ let cache = cx.cache();
+ let deref_type = impl_.inner_impl().trait_.as_ref().unwrap();
+ let (target, real_target) = impl_
+ .inner_impl()
+ .items
+ .iter()
+ .find_map(|item| match *item.kind {
+ clean::AssocTypeItem(box ref t, _) => Some(match *t {
+ clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
+ _ => (&t.type_, &t.type_),
+ }),
+ _ => None,
+ })
+ .expect("Expected associated type binding");
+ debug!("Render deref methods for {:#?}, target {:#?}", impl_.inner_impl().for_, target);
+ let what =
+ AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut };
+ if let Some(did) = target.def_id(cache) {
+ if let Some(type_did) = impl_.inner_impl().for_.def_id(cache) {
+ // `impl Deref<Target = S> for S`
+ if did == type_did || !derefs.insert(did) {
+ // Avoid infinite cycles
+ return;
+ }
+ }
+ render_assoc_items_inner(w, cx, container_item, did, what, derefs);
+ } else if let Some(prim) = target.primitive_type() {
+ if let Some(&did) = cache.primitive_locations.get(&prim) {
+ render_assoc_items_inner(w, cx, container_item, did, what, derefs);
+ }
+ }
+}
+
+fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool {
+ let self_type_opt = match *item.kind {
+ clean::MethodItem(ref method, _) => method.decl.self_type(),
+ clean::TyMethodItem(ref method) => method.decl.self_type(),
+ _ => None,
+ };
+
+ if let Some(self_ty) = self_type_opt {
+ let (by_mut_ref, by_box, by_value) = match self_ty {
+ SelfTy::SelfBorrowed(_, mutability)
+ | SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => {
+ (mutability == Mutability::Mut, false, false)
+ }
+ SelfTy::SelfExplicit(clean::Type::Path { path }) => {
+ (false, Some(path.def_id()) == tcx.lang_items().owned_box(), false)
+ }
+ SelfTy::SelfValue => (false, false, true),
+ _ => (false, false, false),
+ };
+
+ (deref_mut_ || !by_mut_ref) && !by_box && !by_value
+ } else {
+ false
+ }
+}
+
+fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
+ let mut out = Buffer::html();
+
+ if let Some((did, ty)) = decl.output.as_return().and_then(|t| Some((t.def_id(cx.cache())?, t)))
+ {
+ if let Some(impls) = cx.cache().impls.get(&did) {
+ for i in impls {
+ let impl_ = i.inner_impl();
+ if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache())
+ {
+ // Two different types might have the same did,
+ // without actually being the same.
+ continue;
+ }
+ if let Some(trait_) = &impl_.trait_ {
+ let trait_did = trait_.def_id();
+
+ if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable) {
+ if out.is_empty() {
+ write!(
+ &mut out,
+ "<span class=\"notable\">Notable traits for {}</span>\
+ <code class=\"content\">",
+ impl_.for_.print(cx)
+ );
+ }
+
+ //use the "where" class here to make it small
+ write!(
+ &mut out,
+ "<span class=\"where fmt-newline\">{}</span>",
+ impl_.print(false, cx)
+ );
+ for it in &impl_.items {
+ if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
+ out.push_str("<span class=\"where fmt-newline\"> ");
+ let empty_set = FxHashSet::default();
+ let src_link =
+ AssocItemLink::GotoSource(trait_did.into(), &empty_set);
+ assoc_type(
+ &mut out,
+ it,
+ &tydef.generics,
+ &[], // intentionally leaving out bounds
+ Some(&tydef.type_),
+ src_link,
+ 0,
+ cx,
+ );
+ out.push_str(";</span>");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if !out.is_empty() {
+ out.insert_str(
+ 0,
+ "<span class=\"notable-traits\"><span class=\"notable-traits-tooltip\">ⓘ\
+ <span class=\"notable-traits-tooltiptext\"><span class=\"docblock\">",
+ );
+ out.push_str("</code></span></span></span></span>");
+ }
+
+ out.into_inner()
+}
+
+#[derive(Clone, Copy, Debug)]
+struct ImplRenderingParameters {
+ show_def_docs: bool,
+ show_default_items: bool,
+ /// Whether or not to show methods.
+ show_non_assoc_items: bool,
+ toggle_open_by_default: bool,
+}
+
+fn render_impl(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ i: &Impl,
+ parent: &clean::Item,
+ link: AssocItemLink<'_>,
+ render_mode: RenderMode,
+ use_absolute: Option<bool>,
+ aliases: &[String],
+ rendering_params: ImplRenderingParameters,
+) {
+ let shared = Rc::clone(&cx.shared);
+ let cache = &shared.cache;
+ let traits = &cache.traits;
+ let trait_ = i.trait_did().map(|did| &traits[&did]);
+ let mut close_tags = String::new();
+
+ // For trait implementations, the `interesting` output contains all methods that have doc
+ // comments, and the `boring` output contains all methods that do not. The distinction is
+ // used to allow hiding the boring methods.
+ // `containing_item` is used for rendering stability info. If the parent is a trait impl,
+ // `containing_item` will the grandparent, since trait impls can't have stability attached.
+ fn doc_impl_item(
+ boring: &mut Buffer,
+ interesting: &mut Buffer,
+ cx: &mut Context<'_>,
+ item: &clean::Item,
+ parent: &clean::Item,
+ containing_item: &clean::Item,
+ link: AssocItemLink<'_>,
+ render_mode: RenderMode,
+ is_default_item: bool,
+ trait_: Option<&clean::Trait>,
+ rendering_params: ImplRenderingParameters,
+ ) {
+ let item_type = item.type_();
+ let name = item.name.as_ref().unwrap();
+
+ let render_method_item = rendering_params.show_non_assoc_items
+ && match render_mode {
+ RenderMode::Normal => true,
+ RenderMode::ForDeref { mut_: deref_mut_ } => {
+ should_render_item(item, deref_mut_, cx.tcx())
+ }
+ };
+
+ let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" };
+
+ let mut doc_buffer = Buffer::empty_from(boring);
+ let mut info_buffer = Buffer::empty_from(boring);
+ let mut short_documented = true;
+
+ if render_method_item {
+ if !is_default_item {
+ if let Some(t) = trait_ {
+ // The trait item may have been stripped so we might not
+ // find any documentation or stability for it.
+ if let Some(it) = t.items.iter().find(|i| i.name == item.name) {
+ // We need the stability of the item from the trait
+ // because impls can't have a stability.
+ if item.doc_value().is_some() {
+ document_item_info(&mut info_buffer, cx, it, Some(parent));
+ document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
+ short_documented = false;
+ } else {
+ // In case the item isn't documented,
+ // provide short documentation from the trait.
+ document_short(
+ &mut doc_buffer,
+ it,
+ cx,
+ link,
+ parent,
+ rendering_params.show_def_docs,
+ );
+ }
+ }
+ } else {
+ document_item_info(&mut info_buffer, cx, item, Some(parent));
+ if rendering_params.show_def_docs {
+ document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
+ short_documented = false;
+ }
+ }
+ } else {
+ document_short(
+ &mut doc_buffer,
+ item,
+ cx,
+ link,
+ parent,
+ rendering_params.show_def_docs,
+ );
+ }
+ }
+ let w = if short_documented && trait_.is_some() { interesting } else { boring };
+
+ let toggled = !doc_buffer.is_empty();
+ if toggled {
+ let method_toggle_class =
+ if item_type == ItemType::Method { " method-toggle" } else { "" };
+ write!(w, "<details class=\"rustdoc-toggle{}\" open><summary>", method_toggle_class);
+ }
+ match &*item.kind {
+ clean::MethodItem(..) | clean::TyMethodItem(_) => {
+ // Only render when the method is not static or we allow static methods
+ if render_method_item {
+ let id = cx.derive_id(format!("{}.{}", item_type, name));
+ let source_id = trait_
+ .and_then(|trait_| {
+ trait_.items.iter().find(|item| {
+ item.name.map(|n| n.as_str().eq(name.as_str())).unwrap_or(false)
+ })
+ })
+ .map(|item| format!("{}.{}", item.type_(), name));
+ write!(
+ w,
+ "<section id=\"{}\" class=\"{}{} has-srclink\">",
+ id, item_type, in_trait_class,
+ );
+ render_rightside(w, cx, item, containing_item, render_mode);
+ if trait_.is_some() {
+ // Anchors are only used on trait impls.
+ write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ }
+ w.write_str("<h4 class=\"code-header\">");
+ render_assoc_item(
+ w,
+ item,
+ link.anchor(source_id.as_ref().unwrap_or(&id)),
+ ItemType::Impl,
+ cx,
+ render_mode,
+ );
+ w.write_str("</h4>");
+ w.write_str("</section>");
+ }
+ }
+ kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => {
+ let source_id = format!("{}.{}", item_type, name);
+ let id = cx.derive_id(source_id.clone());
+ write!(
+ w,
+ "<section id=\"{}\" class=\"{}{} has-srclink\">",
+ id, item_type, in_trait_class
+ );
+ render_rightside(w, cx, item, containing_item, render_mode);
+ if trait_.is_some() {
+ // Anchors are only used on trait impls.
+ write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ }
+ w.write_str("<h4 class=\"code-header\">");
+ assoc_const(
+ w,
+ item,
+ ty,
+ match kind {
+ clean::TyAssocConstItem(_) => None,
+ clean::AssocConstItem(_, default) => Some(default),
+ _ => unreachable!(),
+ },
+ link.anchor(if trait_.is_some() { &source_id } else { &id }),
+ "",
+ cx,
+ );
+ w.write_str("</h4>");
+ w.write_str("</section>");
+ }
+ clean::TyAssocTypeItem(generics, bounds) => {
+ let source_id = format!("{}.{}", item_type, name);
+ let id = cx.derive_id(source_id.clone());
+ write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class);
+ if trait_.is_some() {
+ // Anchors are only used on trait impls.
+ write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ }
+ w.write_str("<h4 class=\"code-header\">");
+ assoc_type(
+ w,
+ item,
+ generics,
+ bounds,
+ None,
+ link.anchor(if trait_.is_some() { &source_id } else { &id }),
+ 0,
+ cx,
+ );
+ w.write_str("</h4>");
+ w.write_str("</section>");
+ }
+ clean::AssocTypeItem(tydef, _bounds) => {
+ let source_id = format!("{}.{}", item_type, name);
+ let id = cx.derive_id(source_id.clone());
+ write!(
+ w,
+ "<section id=\"{}\" class=\"{}{} has-srclink\">",
+ id, item_type, in_trait_class
+ );
+ if trait_.is_some() {
+ // Anchors are only used on trait impls.
+ write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ }
+ w.write_str("<h4 class=\"code-header\">");
+ assoc_type(
+ w,
+ item,
+ &tydef.generics,
+ &[], // intentionally leaving out bounds
+ Some(tydef.item_type.as_ref().unwrap_or(&tydef.type_)),
+ link.anchor(if trait_.is_some() { &source_id } else { &id }),
+ 0,
+ cx,
+ );
+ w.write_str("</h4>");
+ w.write_str("</section>");
+ }
+ clean::StrippedItem(..) => return,
+ _ => panic!("can't make docs for trait item with name {:?}", item.name),
+ }
+
+ w.push_buffer(info_buffer);
+ if toggled {
+ w.write_str("</summary>");
+ w.push_buffer(doc_buffer);
+ w.push_str("</details>");
+ }
+ }
+
+ let mut impl_items = Buffer::empty_from(w);
+ let mut default_impl_items = Buffer::empty_from(w);
+
+ for trait_item in &i.inner_impl().items {
+ doc_impl_item(
+ &mut default_impl_items,
+ &mut impl_items,
+ cx,
+ trait_item,
+ if trait_.is_some() { &i.impl_item } else { parent },
+ parent,
+ link,
+ render_mode,
+ false,
+ trait_.map(|t| &t.trait_),
+ rendering_params,
+ );
+ }
+
+ fn render_default_items(
+ boring: &mut Buffer,
+ interesting: &mut Buffer,
+ cx: &mut Context<'_>,
+ t: &clean::Trait,
+ i: &clean::Impl,
+ parent: &clean::Item,
+ containing_item: &clean::Item,
+ render_mode: RenderMode,
+ rendering_params: ImplRenderingParameters,
+ ) {
+ for trait_item in &t.items {
+ let n = trait_item.name;
+ if i.items.iter().any(|m| m.name == n) {
+ continue;
+ }
+ let did = i.trait_.as_ref().unwrap().def_id();
+ let provided_methods = i.provided_trait_methods(cx.tcx());
+ let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods);
+
+ doc_impl_item(
+ boring,
+ interesting,
+ cx,
+ trait_item,
+ parent,
+ containing_item,
+ assoc_link,
+ render_mode,
+ true,
+ Some(t),
+ rendering_params,
+ );
+ }
+ }
+
+ // If we've implemented a trait, then also emit documentation for all
+ // default items which weren't overridden in the implementation block.
+ // We don't emit documentation for default items if they appear in the
+ // Implementations on Foreign Types or Implementors sections.
+ if rendering_params.show_default_items {
+ if let Some(t) = trait_ {
+ render_default_items(
+ &mut default_impl_items,
+ &mut impl_items,
+ cx,
+ &t.trait_,
+ i.inner_impl(),
+ &i.impl_item,
+ parent,
+ render_mode,
+ rendering_params,
+ );
+ }
+ }
+ if render_mode == RenderMode::Normal {
+ let toggled = !(impl_items.is_empty() && default_impl_items.is_empty());
+ if toggled {
+ close_tags.insert_str(0, "</details>");
+ write!(
+ w,
+ "<details class=\"rustdoc-toggle implementors-toggle\"{}>",
+ if rendering_params.toggle_open_by_default { " open" } else { "" }
+ );
+ write!(w, "<summary>")
+ }
+ render_impl_summary(
+ w,
+ cx,
+ i,
+ parent,
+ parent,
+ rendering_params.show_def_docs,
+ use_absolute,
+ aliases,
+ );
+ if toggled {
+ write!(w, "</summary>")
+ }
+
+ if let Some(ref dox) = i.impl_item.collapsed_doc_value() {
+ if trait_.is_none() && i.inner_impl().items.is_empty() {
+ w.write_str(
+ "<div class=\"item-info\">\
+ <div class=\"stab empty-impl\">This impl block contains no items.</div>
+ </div>",
+ );
+ }
+ write!(
+ w,
+ "<div class=\"docblock\">{}</div>",
+ Markdown {
+ content: &*dox,
+ links: &i.impl_item.links(cx),
+ ids: &mut cx.id_map,
+ error_codes: cx.shared.codes,
+ edition: cx.shared.edition(),
+ playground: &cx.shared.playground,
+ heading_offset: HeadingOffset::H4
+ }
+ .into_string()
+ );
+ }
+ }
+ if !default_impl_items.is_empty() || !impl_items.is_empty() {
+ w.write_str("<div class=\"impl-items\">");
+ w.push_buffer(default_impl_items);
+ w.push_buffer(impl_items);
+ close_tags.insert_str(0, "</div>");
+ }
+ w.write_str(&close_tags);
+}
+
+// Render the items that appear on the right side of methods, impls, and
+// associated types. For example "1.0.0 (const: 1.39.0) · source".
+fn render_rightside(
+ w: &mut Buffer,
+ cx: &Context<'_>,
+ item: &clean::Item,
+ containing_item: &clean::Item,
+ render_mode: RenderMode,
+) {
+ let tcx = cx.tcx();
+
+ // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
+ // this condition.
+ let (const_stability, const_stable_since) = match render_mode {
+ RenderMode::Normal => (item.const_stability(tcx), containing_item.const_stable_since(tcx)),
+ RenderMode::ForDeref { .. } => (None, None),
+ };
+
+ let mut rightside = Buffer::new();
+ let has_stability = render_stability_since_raw(
+ &mut rightside,
+ item.stable_since(tcx),
+ const_stability,
+ containing_item.stable_since(tcx),
+ const_stable_since,
+ );
+ let mut srclink = Buffer::empty_from(w);
+ write_srclink(cx, item, &mut srclink);
+ if has_stability && !srclink.is_empty() {
+ rightside.write_str(" · ");
+ }
+ rightside.push_buffer(srclink);
+ if !rightside.is_empty() {
+ write!(w, "<span class=\"rightside\">{}</span>", rightside.into_inner());
+ }
+}
+
+pub(crate) fn render_impl_summary(
+ w: &mut Buffer,
+ cx: &mut Context<'_>,
+ i: &Impl,
+ parent: &clean::Item,
+ containing_item: &clean::Item,
+ show_def_docs: bool,
+ use_absolute: Option<bool>,
+ // This argument is used to reference same type with different paths to avoid duplication
+ // in documentation pages for trait with automatic implementations like "Send" and "Sync".
+ aliases: &[String],
+) {
+ let id =
+ cx.derive_id(get_id_for_impl(&i.inner_impl().for_, i.inner_impl().trait_.as_ref(), cx));
+ let aliases = if aliases.is_empty() {
+ String::new()
+ } else {
+ format!(" data-aliases=\"{}\"", aliases.join(","))
+ };
+ write!(w, "<section id=\"{}\" class=\"impl has-srclink\"{}>", id, aliases);
+ render_rightside(w, cx, &i.impl_item, containing_item, RenderMode::Normal);
+ write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ write!(w, "<h3 class=\"code-header in-band\">");
+
+ if let Some(use_absolute) = use_absolute {
+ write!(w, "{}", i.inner_impl().print(use_absolute, cx));
+ if show_def_docs {
+ for it in &i.inner_impl().items {
+ if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
+ w.write_str("<span class=\"where fmt-newline\"> ");
+ assoc_type(
+ w,
+ it,
+ &tydef.generics,
+ &[], // intentionally leaving out bounds
+ Some(&tydef.type_),
+ AssocItemLink::Anchor(None),
+ 0,
+ cx,
+ );
+ w.write_str(";</span>");
+ }
+ }
+ }
+ } else {
+ write!(w, "{}", i.inner_impl().print(false, cx));
+ }
+ write!(w, "</h3>");
+
+ let is_trait = i.inner_impl().trait_.is_some();
+ if is_trait {
+ if let Some(portability) = portability(&i.impl_item, Some(parent)) {
+ write!(w, "<span class=\"item-info\">{}</span>", portability);
+ }
+ }
+
+ w.write_str("</section>");
+}
+
+fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
+ if it.is_struct()
+ || it.is_trait()
+ || it.is_primitive()
+ || it.is_union()
+ || it.is_enum()
+ || it.is_mod()
+ || it.is_typedef()
+ {
+ write!(
+ buffer,
+ "<h2 class=\"location\"><a href=\"#\">{}{}</a></h2>",
+ match *it.kind {
+ clean::ModuleItem(..) =>
+ if it.is_crate() {
+ "Crate "
+ } else {
+ "Module "
+ },
+ _ => "",
+ },
+ it.name.as_ref().unwrap()
+ );
+ }
+
+ buffer.write_str("<div class=\"sidebar-elems\">");
+ if it.is_crate() {
+ write!(buffer, "<div class=\"block\"><ul>");
+ if let Some(ref version) = cx.cache().crate_version {
+ write!(buffer, "<li class=\"version\">Version {}</li>", Escape(version));
+ }
+ write!(buffer, "<li><a id=\"all-types\" href=\"all.html\">All Items</a></li>");
+ buffer.write_str("</ul></div>");
+ }
+
+ match *it.kind {
+ clean::StructItem(ref s) => sidebar_struct(cx, buffer, it, s),
+ clean::TraitItem(ref t) => sidebar_trait(cx, buffer, it, t),
+ clean::PrimitiveItem(_) => sidebar_primitive(cx, buffer, it),
+ clean::UnionItem(ref u) => sidebar_union(cx, buffer, it, u),
+ clean::EnumItem(ref e) => sidebar_enum(cx, buffer, it, e),
+ clean::TypedefItem(_) => sidebar_typedef(cx, buffer, it),
+ clean::ModuleItem(ref m) => sidebar_module(buffer, &m.items),
+ clean::ForeignTypeItem => sidebar_foreign_type(cx, buffer, it),
+ _ => {}
+ }
+
+ // The sidebar is designed to display sibling functions, modules and
+ // other miscellaneous information. since there are lots of sibling
+ // items (and that causes quadratic growth in large modules),
+ // we refactor common parts into a shared JavaScript file per module.
+ // still, we don't move everything into JS because we want to preserve
+ // as much HTML as possible in order to allow non-JS-enabled browsers
+ // to navigate the documentation (though slightly inefficiently).
+
+ if !it.is_mod() {
+ let path: String = cx.current.iter().map(|s| s.as_str()).intersperse("::").collect();
+
+ write!(buffer, "<h2 class=\"location\"><a href=\"index.html\">In {}</a></h2>", path);
+ }
+
+ // Closes sidebar-elems div.
+ buffer.write_str("</div>");
+}
+
+fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
+ if used_links.insert(url.clone()) {
+ return url;
+ }
+ let mut add = 1;
+ while !used_links.insert(format!("{}-{}", url, add)) {
+ add += 1;
+ }
+ format!("{}-{}", url, add)
+}
+
+struct SidebarLink {
+ name: Symbol,
+ url: String,
+}
+
+impl fmt::Display for SidebarLink {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "<a href=\"#{}\">{}</a>", self.url, self.name)
+ }
+}
+
+impl PartialEq for SidebarLink {
+ fn eq(&self, other: &Self) -> bool {
+ self.url == other.url
+ }
+}
+
+impl Eq for SidebarLink {}
+
+impl PartialOrd for SidebarLink {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for SidebarLink {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.url.cmp(&other.url)
+ }
+}
+
+fn get_methods(
+ i: &clean::Impl,
+ for_deref: bool,
+ used_links: &mut FxHashSet<String>,
+ deref_mut: bool,
+ tcx: TyCtxt<'_>,
+) -> Vec<SidebarLink> {
+ i.items
+ .iter()
+ .filter_map(|item| match item.name {
+ Some(name) if !name.is_empty() && item.is_method() => {
+ if !for_deref || should_render_item(item, deref_mut, tcx) {
+ Some(SidebarLink {
+ name,
+ url: get_next_url(used_links, format!("{}.{}", ItemType::Method, name)),
+ })
+ } else {
+ None
+ }
+ }
+ _ => None,
+ })
+ .collect::<Vec<_>>()
+}
+
+fn get_associated_constants(
+ i: &clean::Impl,
+ used_links: &mut FxHashSet<String>,
+) -> Vec<SidebarLink> {
+ i.items
+ .iter()
+ .filter_map(|item| match item.name {
+ Some(name) if !name.is_empty() && item.is_associated_const() => Some(SidebarLink {
+ name,
+ url: get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)),
+ }),
+ _ => None,
+ })
+ .collect::<Vec<_>>()
+}
+
+// The point is to url encode any potential character from a type with genericity.
+fn small_url_encode(s: String) -> String {
+ let mut st = String::new();
+ let mut last_match = 0;
+ for (idx, c) in s.char_indices() {
+ let escaped = match c {
+ '<' => "%3C",
+ '>' => "%3E",
+ ' ' => "%20",
+ '?' => "%3F",
+ '\'' => "%27",
+ '&' => "%26",
+ ',' => "%2C",
+ ':' => "%3A",
+ ';' => "%3B",
+ '[' => "%5B",
+ ']' => "%5D",
+ '"' => "%22",
+ _ => continue,
+ };
+
+ st += &s[last_match..idx];
+ st += escaped;
+ // NOTE: we only expect single byte characters here - which is fine as long as we
+ // only match single byte characters
+ last_match = idx + 1;
+ }
+
+ if last_match != 0 {
+ st += &s[last_match..];
+ st
+ } else {
+ s
+ }
+}
+
+fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
+ let did = it.item_id.expect_def_id();
+ let cache = cx.cache();
+
+ if let Some(v) = cache.impls.get(&did) {
+ let mut used_links = FxHashSet::default();
+ let mut id_map = IdMap::new();
+
+ {
+ let used_links_bor = &mut used_links;
+ let mut assoc_consts = v
+ .iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor))
+ .collect::<Vec<_>>();
+ if !assoc_consts.is_empty() {
+ // We want links' order to be reproducible so we don't use unstable sort.
+ assoc_consts.sort();
+
+ print_sidebar_block(
+ out,
+ "implementations",
+ "Associated Constants",
+ assoc_consts.iter(),
+ );
+ }
+ let mut methods = v
+ .iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx()))
+ .collect::<Vec<_>>();
+ if !methods.is_empty() {
+ // We want links' order to be reproducible so we don't use unstable sort.
+ methods.sort();
+
+ print_sidebar_block(out, "implementations", "Methods", methods.iter());
+ }
+ }
+
+ if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
+ if let Some(impl_) =
+ v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
+ {
+ let mut derefs = FxHashSet::default();
+ derefs.insert(did);
+ sidebar_deref_methods(cx, out, impl_, v, &mut derefs);
+ }
+
+ let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
+ let mut links = FxHashSet::default();
+
+ let mut ret = impls
+ .iter()
+ .filter_map(|it| {
+ let trait_ = it.inner_impl().trait_.as_ref()?;
+ let encoded =
+ id_map.derive(get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx));
+
+ let i_display = format!("{:#}", trait_.print(cx));
+ let out = Escape(&i_display);
+ let prefix = match it.inner_impl().polarity {
+ ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
+ ty::ImplPolarity::Negative => "!",
+ };
+ let generated = format!("<a href=\"#{}\">{}{}</a>", encoded, prefix, out);
+ if links.insert(generated.clone()) { Some(generated) } else { None }
+ })
+ .collect::<Vec<String>>();
+ ret.sort();
+ ret
+ };
+
+ let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
+ v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
+ let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
+ concrete.into_iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_blanket());
+
+ let concrete_format = format_impls(concrete, &mut id_map);
+ let synthetic_format = format_impls(synthetic, &mut id_map);
+ let blanket_format = format_impls(blanket_impl, &mut id_map);
+
+ if !concrete_format.is_empty() {
+ print_sidebar_block(
+ out,
+ "trait-implementations",
+ "Trait Implementations",
+ concrete_format.iter(),
+ );
+ }
+
+ if !synthetic_format.is_empty() {
+ print_sidebar_block(
+ out,
+ "synthetic-implementations",
+ "Auto Trait Implementations",
+ synthetic_format.iter(),
+ );
+ }
+
+ if !blanket_format.is_empty() {
+ print_sidebar_block(
+ out,
+ "blanket-implementations",
+ "Blanket Implementations",
+ blanket_format.iter(),
+ );
+ }
+ }
+ }
+}
+
+fn sidebar_deref_methods(
+ cx: &Context<'_>,
+ out: &mut Buffer,
+ impl_: &Impl,
+ v: &[Impl],
+ derefs: &mut FxHashSet<DefId>,
+) {
+ let c = cx.cache();
+
+ debug!("found Deref: {:?}", impl_);
+ if let Some((target, real_target)) =
+ impl_.inner_impl().items.iter().find_map(|item| match *item.kind {
+ clean::AssocTypeItem(box ref t, _) => Some(match *t {
+ clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
+ _ => (&t.type_, &t.type_),
+ }),
+ _ => None,
+ })
+ {
+ debug!("found target, real_target: {:?} {:?}", target, real_target);
+ if let Some(did) = target.def_id(c) {
+ if let Some(type_did) = impl_.inner_impl().for_.def_id(c) {
+ // `impl Deref<Target = S> for S`
+ if did == type_did || !derefs.insert(did) {
+ // Avoid infinite cycles
+ return;
+ }
+ }
+ }
+ let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
+ let inner_impl = target
+ .def_id(c)
+ .or_else(|| {
+ target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned())
+ })
+ .and_then(|did| c.impls.get(&did));
+ if let Some(impls) = inner_impl {
+ debug!("found inner_impl: {:?}", impls);
+ let mut used_links = FxHashSet::default();
+ let mut ret = impls
+ .iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| {
+ get_methods(i.inner_impl(), true, &mut used_links, deref_mut, cx.tcx())
+ })
+ .collect::<Vec<_>>();
+ if !ret.is_empty() {
+ let id = if let Some(target_def_id) = real_target.def_id(c) {
+ cx.deref_id_map.get(&target_def_id).expect("Deref section without derived id")
+ } else {
+ "deref-methods"
+ };
+ let title = format!(
+ "Methods from {}&lt;Target={}&gt;",
+ Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))),
+ Escape(&format!("{:#}", real_target.print(cx))),
+ );
+ // We want links' order to be reproducible so we don't use unstable sort.
+ ret.sort();
+ print_sidebar_block(out, id, &title, ret.iter());
+ }
+ }
+
+ // Recurse into any further impls that might exist for `target`
+ if let Some(target_did) = target.def_id(c) {
+ if let Some(target_impls) = c.impls.get(&target_did) {
+ if let Some(target_deref_impl) = target_impls.iter().find(|i| {
+ i.inner_impl()
+ .trait_
+ .as_ref()
+ .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
+ .unwrap_or(false)
+ }) {
+ sidebar_deref_methods(cx, out, target_deref_impl, target_impls, derefs);
+ }
+ }
+ }
+ }
+}
+
+fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clean::Struct) {
+ let mut sidebar = Buffer::new();
+ let fields = get_struct_fields_name(&s.fields);
+
+ if !fields.is_empty() {
+ match s.struct_type {
+ CtorKind::Fictive => {
+ print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter());
+ }
+ CtorKind::Fn => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"),
+ CtorKind::Const => {}
+ }
+ }
+
+ sidebar_assoc_items(cx, &mut sidebar, it);
+
+ if !sidebar.is_empty() {
+ write!(buf, "<section>{}</section>", sidebar.into_inner());
+ }
+}
+
+fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String {
+ match trait_ {
+ Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))),
+ None => small_url_encode(format!("impl-{:#}", for_.print(cx))),
+ }
+}
+
+fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
+ match *item.kind {
+ clean::ItemKind::ImplItem(ref i) => {
+ i.trait_.as_ref().map(|trait_| {
+ // Alternative format produces no URLs,
+ // so this parameter does nothing.
+ (format!("{:#}", i.for_.print(cx)), get_id_for_impl(&i.for_, Some(trait_), cx))
+ })
+ }
+ _ => None,
+ }
+}
+
+/// Don't call this function directly!!! Use `print_sidebar_title` or `print_sidebar_block` instead!
+fn print_sidebar_title_inner(buf: &mut Buffer, id: &str, title: &str) {
+ write!(
+ buf,
+ "<h3 class=\"sidebar-title\">\
+ <a href=\"#{}\">{}</a>\
+ </h3>",
+ id, title
+ );
+}
+
+fn print_sidebar_title(buf: &mut Buffer, id: &str, title: &str) {
+ buf.push_str("<div class=\"block\">");
+ print_sidebar_title_inner(buf, id, title);
+ buf.push_str("</div>");
+}
+
+fn print_sidebar_block(
+ buf: &mut Buffer,
+ id: &str,
+ title: &str,
+ items: impl Iterator<Item = impl fmt::Display>,
+) {
+ buf.push_str("<div class=\"block\">");
+ print_sidebar_title_inner(buf, id, title);
+ buf.push_str("<ul>");
+ for item in items {
+ write!(buf, "<li>{}</li>", item);
+ }
+ buf.push_str("</ul></div>");
+}
+
+fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) {
+ buf.write_str("<section>");
+
+ fn print_sidebar_section(
+ out: &mut Buffer,
+ items: &[clean::Item],
+ id: &str,
+ title: &str,
+ filter: impl Fn(&clean::Item) -> bool,
+ mapper: impl Fn(&str) -> String,
+ ) {
+ let mut items: Vec<&str> = items
+ .iter()
+ .filter_map(|m| match m.name {
+ Some(ref name) if filter(m) => Some(name.as_str()),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+
+ if !items.is_empty() {
+ items.sort_unstable();
+ print_sidebar_block(out, id, title, items.into_iter().map(mapper));
+ }
+ }
+
+ print_sidebar_section(
+ buf,
+ &t.items,
+ "required-associated-types",
+ "Required Associated Types",
+ |m| m.is_ty_associated_type(),
+ |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocType),
+ );
+
+ print_sidebar_section(
+ buf,
+ &t.items,
+ "provided-associated-types",
+ "Provided Associated Types",
+ |m| m.is_associated_type(),
+ |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocType),
+ );
+
+ print_sidebar_section(
+ buf,
+ &t.items,
+ "required-associated-consts",
+ "Required Associated Constants",
+ |m| m.is_ty_associated_const(),
+ |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocConst),
+ );
+
+ print_sidebar_section(
+ buf,
+ &t.items,
+ "provided-associated-consts",
+ "Provided Associated Constants",
+ |m| m.is_associated_const(),
+ |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocConst),
+ );
+
+ print_sidebar_section(
+ buf,
+ &t.items,
+ "required-methods",
+ "Required Methods",
+ |m| m.is_ty_method(),
+ |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::TyMethod),
+ );
+
+ print_sidebar_section(
+ buf,
+ &t.items,
+ "provided-methods",
+ "Provided Methods",
+ |m| m.is_method(),
+ |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::Method),
+ );
+
+ if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) {
+ let mut res = implementors
+ .iter()
+ .filter(|i| !i.is_on_local_type(cx))
+ .filter_map(|i| extract_for_impl_name(&i.impl_item, cx))
+ .collect::<Vec<_>>();
+
+ if !res.is_empty() {
+ res.sort();
+ print_sidebar_block(
+ buf,
+ "foreign-impls",
+ "Implementations on Foreign Types",
+ res.iter().map(|(name, id)| format!("<a href=\"#{}\">{}</a>", id, Escape(name))),
+ );
+ }
+ }
+
+ sidebar_assoc_items(cx, buf, it);
+
+ print_sidebar_title(buf, "implementors", "Implementors");
+ if t.is_auto(cx.tcx()) {
+ print_sidebar_title(buf, "synthetic-implementors", "Auto Implementors");
+ }
+
+ buf.push_str("</section>")
+}
+
+fn sidebar_primitive(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
+ let mut sidebar = Buffer::new();
+ sidebar_assoc_items(cx, &mut sidebar, it);
+
+ if !sidebar.is_empty() {
+ write!(buf, "<section>{}</section>", sidebar.into_inner());
+ }
+}
+
+fn sidebar_typedef(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
+ let mut sidebar = Buffer::new();
+ sidebar_assoc_items(cx, &mut sidebar, it);
+
+ if !sidebar.is_empty() {
+ write!(buf, "<section>{}</section>", sidebar.into_inner());
+ }
+}
+
+fn get_struct_fields_name(fields: &[clean::Item]) -> Vec<String> {
+ let mut fields = fields
+ .iter()
+ .filter(|f| matches!(*f.kind, clean::StructFieldItem(..)))
+ .filter_map(|f| {
+ f.name.map(|name| format!("<a href=\"#structfield.{name}\">{name}</a>", name = name))
+ })
+ .collect::<Vec<_>>();
+ fields.sort();
+ fields
+}
+
+fn sidebar_union(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, u: &clean::Union) {
+ let mut sidebar = Buffer::new();
+ let fields = get_struct_fields_name(&u.fields);
+
+ if !fields.is_empty() {
+ print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter());
+ }
+
+ sidebar_assoc_items(cx, &mut sidebar, it);
+
+ if !sidebar.is_empty() {
+ write!(buf, "<section>{}</section>", sidebar.into_inner());
+ }
+}
+
+fn sidebar_enum(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, e: &clean::Enum) {
+ let mut sidebar = Buffer::new();
+
+ let mut variants = e
+ .variants()
+ .filter_map(|v| {
+ v.name
+ .as_ref()
+ .map(|name| format!("<a href=\"#variant.{name}\">{name}</a>", name = name))
+ })
+ .collect::<Vec<_>>();
+ if !variants.is_empty() {
+ variants.sort_unstable();
+ print_sidebar_block(&mut sidebar, "variants", "Variants", variants.iter());
+ }
+
+ sidebar_assoc_items(cx, &mut sidebar, it);
+
+ if !sidebar.is_empty() {
+ write!(buf, "<section>{}</section>", sidebar.into_inner());
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+enum ItemSection {
+ Reexports,
+ PrimitiveTypes,
+ Modules,
+ Macros,
+ Structs,
+ Enums,
+ Constants,
+ Statics,
+ Traits,
+ Functions,
+ TypeDefinitions,
+ Unions,
+ Implementations,
+ TypeMethods,
+ Methods,
+ StructFields,
+ Variants,
+ AssociatedTypes,
+ AssociatedConstants,
+ ForeignTypes,
+ Keywords,
+ OpaqueTypes,
+ AttributeMacros,
+ DeriveMacros,
+ TraitAliases,
+}
+
+impl ItemSection {
+ const ALL: &'static [Self] = {
+ use ItemSection::*;
+ // NOTE: The order here affects the order in the UI.
+ &[
+ Reexports,
+ PrimitiveTypes,
+ Modules,
+ Macros,
+ Structs,
+ Enums,
+ Constants,
+ Statics,
+ Traits,
+ Functions,
+ TypeDefinitions,
+ Unions,
+ Implementations,
+ TypeMethods,
+ Methods,
+ StructFields,
+ Variants,
+ AssociatedTypes,
+ AssociatedConstants,
+ ForeignTypes,
+ Keywords,
+ OpaqueTypes,
+ AttributeMacros,
+ DeriveMacros,
+ TraitAliases,
+ ]
+ };
+
+ fn id(self) -> &'static str {
+ match self {
+ Self::Reexports => "reexports",
+ Self::Modules => "modules",
+ Self::Structs => "structs",
+ Self::Unions => "unions",
+ Self::Enums => "enums",
+ Self::Functions => "functions",
+ Self::TypeDefinitions => "types",
+ Self::Statics => "statics",
+ Self::Constants => "constants",
+ Self::Traits => "traits",
+ Self::Implementations => "impls",
+ Self::TypeMethods => "tymethods",
+ Self::Methods => "methods",
+ Self::StructFields => "fields",
+ Self::Variants => "variants",
+ Self::Macros => "macros",
+ Self::PrimitiveTypes => "primitives",
+ Self::AssociatedTypes => "associated-types",
+ Self::AssociatedConstants => "associated-consts",
+ Self::ForeignTypes => "foreign-types",
+ Self::Keywords => "keywords",
+ Self::OpaqueTypes => "opaque-types",
+ Self::AttributeMacros => "attributes",
+ Self::DeriveMacros => "derives",
+ Self::TraitAliases => "trait-aliases",
+ }
+ }
+
+ fn name(self) -> &'static str {
+ match self {
+ Self::Reexports => "Re-exports",
+ Self::Modules => "Modules",
+ Self::Structs => "Structs",
+ Self::Unions => "Unions",
+ Self::Enums => "Enums",
+ Self::Functions => "Functions",
+ Self::TypeDefinitions => "Type Definitions",
+ Self::Statics => "Statics",
+ Self::Constants => "Constants",
+ Self::Traits => "Traits",
+ Self::Implementations => "Implementations",
+ Self::TypeMethods => "Type Methods",
+ Self::Methods => "Methods",
+ Self::StructFields => "Struct Fields",
+ Self::Variants => "Variants",
+ Self::Macros => "Macros",
+ Self::PrimitiveTypes => "Primitive Types",
+ Self::AssociatedTypes => "Associated Types",
+ Self::AssociatedConstants => "Associated Constants",
+ Self::ForeignTypes => "Foreign Types",
+ Self::Keywords => "Keywords",
+ Self::OpaqueTypes => "Opaque Types",
+ Self::AttributeMacros => "Attribute Macros",
+ Self::DeriveMacros => "Derive Macros",
+ Self::TraitAliases => "Trait Aliases",
+ }
+ }
+}
+
+fn item_ty_to_section(ty: ItemType) -> ItemSection {
+ match ty {
+ ItemType::ExternCrate | ItemType::Import => ItemSection::Reexports,
+ ItemType::Module => ItemSection::Modules,
+ ItemType::Struct => ItemSection::Structs,
+ ItemType::Union => ItemSection::Unions,
+ ItemType::Enum => ItemSection::Enums,
+ ItemType::Function => ItemSection::Functions,
+ ItemType::Typedef => ItemSection::TypeDefinitions,
+ ItemType::Static => ItemSection::Statics,
+ ItemType::Constant => ItemSection::Constants,
+ ItemType::Trait => ItemSection::Traits,
+ ItemType::Impl => ItemSection::Implementations,
+ ItemType::TyMethod => ItemSection::TypeMethods,
+ ItemType::Method => ItemSection::Methods,
+ ItemType::StructField => ItemSection::StructFields,
+ ItemType::Variant => ItemSection::Variants,
+ ItemType::Macro => ItemSection::Macros,
+ ItemType::Primitive => ItemSection::PrimitiveTypes,
+ ItemType::AssocType => ItemSection::AssociatedTypes,
+ ItemType::AssocConst => ItemSection::AssociatedConstants,
+ ItemType::ForeignType => ItemSection::ForeignTypes,
+ ItemType::Keyword => ItemSection::Keywords,
+ ItemType::OpaqueTy => ItemSection::OpaqueTypes,
+ ItemType::ProcAttribute => ItemSection::AttributeMacros,
+ ItemType::ProcDerive => ItemSection::DeriveMacros,
+ ItemType::TraitAlias => ItemSection::TraitAliases,
+ }
+}
+
+fn sidebar_module(buf: &mut Buffer, items: &[clean::Item]) {
+ use std::fmt::Write as _;
+
+ let mut sidebar = String::new();
+
+ let item_sections_in_use: FxHashSet<_> = items
+ .iter()
+ .filter(|it| {
+ !it.is_stripped()
+ && it
+ .name
+ .or_else(|| {
+ if let clean::ImportItem(ref i) = *it.kind &&
+ let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None }
+ })
+ .is_some()
+ })
+ .map(|it| item_ty_to_section(it.type_()))
+ .collect();
+ for &sec in ItemSection::ALL.iter().filter(|sec| item_sections_in_use.contains(sec)) {
+ let _ = write!(sidebar, "<li><a href=\"#{}\">{}</a></li>", sec.id(), sec.name());
+ }
+
+ if !sidebar.is_empty() {
+ write!(
+ buf,
+ "<section>\
+ <div class=\"block\">\
+ <ul>{}</ul>\
+ </div>\
+ </section>",
+ sidebar
+ );
+ }
+}
+
+fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
+ let mut sidebar = Buffer::new();
+ sidebar_assoc_items(cx, &mut sidebar, it);
+
+ if !sidebar.is_empty() {
+ write!(buf, "<section>{}</section>", sidebar.into_inner());
+ }
+}
+
+pub(crate) const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang";
+
+/// Returns a list of all paths used in the type.
+/// This is used to help deduplicate imported impls
+/// for reexported types. If any of the contained
+/// types are re-exported, we don't use the corresponding
+/// entry from the js file, as inlining will have already
+/// picked up the impl
+fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
+ let mut out = Vec::new();
+ let mut visited = FxHashSet::default();
+ let mut work = VecDeque::new();
+
+ let mut process_path = |did: DefId| {
+ let get_extern = || cache.external_paths.get(&did).map(|s| s.0.clone());
+ let fqp = cache.exact_paths.get(&did).cloned().or_else(get_extern);
+
+ if let Some(path) = fqp {
+ out.push(join_with_double_colon(&path));
+ }
+ };
+
+ work.push_back(first_ty);
+
+ while let Some(ty) = work.pop_front() {
+ if !visited.insert(ty.clone()) {
+ continue;
+ }
+
+ match ty {
+ clean::Type::Path { path } => process_path(path.def_id()),
+ clean::Type::Tuple(tys) => {
+ work.extend(tys.into_iter());
+ }
+ clean::Type::Slice(ty) => {
+ work.push_back(*ty);
+ }
+ clean::Type::Array(ty, _) => {
+ work.push_back(*ty);
+ }
+ clean::Type::RawPointer(_, ty) => {
+ work.push_back(*ty);
+ }
+ clean::Type::BorrowedRef { type_, .. } => {
+ work.push_back(*type_);
+ }
+ clean::Type::QPath { self_type, trait_, .. } => {
+ work.push_back(*self_type);
+ process_path(trait_.def_id());
+ }
+ _ => {}
+ }
+ }
+ out
+}
+
+const MAX_FULL_EXAMPLES: usize = 5;
+const NUM_VISIBLE_LINES: usize = 10;
+
+/// Generates the HTML for example call locations generated via the --scrape-examples flag.
+fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item) {
+ let tcx = cx.tcx();
+ let def_id = item.item_id.expect_def_id();
+ let key = tcx.def_path_hash(def_id);
+ let Some(call_locations) = cx.shared.call_locations.get(&key) else { return };
+
+ // Generate a unique ID so users can link to this section for a given method
+ let id = cx.id_map.derive("scraped-examples");
+ write!(
+ w,
+ "<div class=\"docblock scraped-example-list\">\
+ <span></span>\
+ <h5 id=\"{id}\">\
+ <a href=\"#{id}\">Examples found in repository</a>\
+ <a class=\"scrape-help\" href=\"{root_path}scrape-examples-help.html\">?</a>\
+ </h5>",
+ root_path = cx.root_path(),
+ id = id
+ );
+
+ // Create a URL to a particular location in a reverse-dependency's source file
+ let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) {
+ let (line_lo, line_hi) = loc.call_expr.line_span;
+ let (anchor, title) = if line_lo == line_hi {
+ ((line_lo + 1).to_string(), format!("line {}", line_lo + 1))
+ } else {
+ (
+ format!("{}-{}", line_lo + 1, line_hi + 1),
+ format!("lines {}-{}", line_lo + 1, line_hi + 1),
+ )
+ };
+ let url = format!("{}{}#{}", cx.root_path(), call_data.url, anchor);
+ (url, title)
+ };
+
+ // Generate the HTML for a single example, being the title and code block
+ let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| -> bool {
+ let contents = match fs::read_to_string(&path) {
+ Ok(contents) => contents,
+ Err(err) => {
+ let span = item.span(tcx).inner();
+ tcx.sess
+ .span_err(span, &format!("failed to read file {}: {}", path.display(), err));
+ return false;
+ }
+ };
+
+ // To reduce file sizes, we only want to embed the source code needed to understand the example, not
+ // the entire file. So we find the smallest byte range that covers all items enclosing examples.
+ assert!(!call_data.locations.is_empty());
+ let min_loc =
+ call_data.locations.iter().min_by_key(|loc| loc.enclosing_item.byte_span.0).unwrap();
+ let byte_min = min_loc.enclosing_item.byte_span.0;
+ let line_min = min_loc.enclosing_item.line_span.0;
+ let max_loc =
+ call_data.locations.iter().max_by_key(|loc| loc.enclosing_item.byte_span.1).unwrap();
+ let byte_max = max_loc.enclosing_item.byte_span.1;
+ let line_max = max_loc.enclosing_item.line_span.1;
+
+ // The output code is limited to that byte range.
+ let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)];
+
+ // The call locations need to be updated to reflect that the size of the program has changed.
+ // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point.
+ let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data
+ .locations
+ .iter()
+ .map(|loc| {
+ let (byte_lo, byte_hi) = loc.call_ident.byte_span;
+ let (line_lo, line_hi) = loc.call_expr.line_span;
+ let byte_range = (byte_lo - byte_min, byte_hi - byte_min);
+
+ let line_range = (line_lo - line_min, line_hi - line_min);
+ let (line_url, line_title) = link_to_loc(call_data, loc);
+
+ (byte_range, (line_range, line_url, line_title))
+ })
+ .unzip();
+
+ let (_, init_url, init_title) = &line_ranges[0];
+ let needs_expansion = line_max - line_min > NUM_VISIBLE_LINES;
+ let locations_encoded = serde_json::to_string(&line_ranges).unwrap();
+
+ write!(
+ w,
+ "<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\
+ <div class=\"scraped-example-title\">\
+ {name} (<a href=\"{url}\">{title}</a>)\
+ </div>\
+ <div class=\"code-wrapper\">",
+ expanded_cls = if needs_expansion { "" } else { "expanded" },
+ name = call_data.display_name,
+ url = init_url,
+ title = init_title,
+ // The locations are encoded as a data attribute, so they can be read
+ // later by the JS for interactions.
+ locations = Escape(&locations_encoded)
+ );
+
+ if line_ranges.len() > 1 {
+ write!(w, r#"<span class="prev">&pr;</span> <span class="next">&sc;</span>"#);
+ }
+
+ if needs_expansion {
+ write!(w, r#"<span class="expand">&varr;</span>"#);
+ }
+
+ // Look for the example file in the source map if it exists, otherwise return a dummy span
+ let file_span = (|| {
+ let source_map = tcx.sess.source_map();
+ let crate_src = tcx.sess.local_crate_source_file.as_ref()?;
+ let abs_crate_src = crate_src.canonicalize().ok()?;
+ let crate_root = abs_crate_src.parent()?.parent()?;
+ let rel_path = path.strip_prefix(crate_root).ok()?;
+ let files = source_map.files();
+ let file = files.iter().find(|file| match &file.name {
+ FileName::Real(RealFileName::LocalPath(other_path)) => rel_path == other_path,
+ _ => false,
+ })?;
+ Some(rustc_span::Span::with_root_ctxt(
+ file.start_pos + BytePos(byte_min),
+ file.start_pos + BytePos(byte_max),
+ ))
+ })()
+ .unwrap_or(rustc_span::DUMMY_SP);
+
+ // The root path is the inverse of Context::current
+ let root_path = vec!["../"; cx.current.len() - 1].join("");
+
+ let mut decoration_info = FxHashMap::default();
+ decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
+ decoration_info.insert("highlight", byte_ranges);
+
+ sources::print_src(
+ w,
+ contents_subset,
+ call_data.edition,
+ file_span,
+ cx,
+ &root_path,
+ Some(highlight::DecorationInfo(decoration_info)),
+ sources::SourceContext::Embedded { offset: line_min },
+ );
+ write!(w, "</div></div>");
+
+ true
+ };
+
+ // The call locations are output in sequence, so that sequence needs to be determined.
+ // Ideally the most "relevant" examples would be shown first, but there's no general algorithm
+ // for determining relevance. Instead, we prefer the smallest examples being likely the easiest to
+ // understand at a glance.
+ let ordered_locations = {
+ let sort_criterion = |(_, call_data): &(_, &CallData)| {
+ // Use the first location because that's what the user will see initially
+ let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
+ hi - lo
+ };
+
+ let mut locs = call_locations.iter().collect::<Vec<_>>();
+ locs.sort_by_key(sort_criterion);
+ locs
+ };
+
+ let mut it = ordered_locations.into_iter().peekable();
+
+ // An example may fail to write if its source can't be read for some reason, so this method
+ // continues iterating until a write succeeds
+ let write_and_skip_failure = |w: &mut Buffer, it: &mut Peekable<_>| {
+ while let Some(example) = it.next() {
+ if write_example(&mut *w, example) {
+ break;
+ }
+ }
+ };
+
+ // Write just one example that's visible by default in the method's description.
+ write_and_skip_failure(w, &mut it);
+
+ // Then add the remaining examples in a hidden section.
+ if it.peek().is_some() {
+ write!(
+ w,
+ "<details class=\"rustdoc-toggle more-examples-toggle\">\
+ <summary class=\"hideme\">\
+ <span>More examples</span>\
+ </summary>\
+ <div class=\"hide-more\">Hide additional examples</div>\
+ <div class=\"more-scraped-examples\">\
+ <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>\
+ <div class=\"more-scraped-examples-inner\">"
+ );
+
+ // Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
+ // make the page arbitrarily huge!
+ for _ in 0..MAX_FULL_EXAMPLES {
+ write_and_skip_failure(w, &mut it);
+ }
+
+ // For the remaining examples, generate a <ul> containing links to the source files.
+ if it.peek().is_some() {
+ write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#);
+ it.for_each(|(_, call_data)| {
+ let (url, _) = link_to_loc(call_data, &call_data.locations[0]);
+ write!(
+ w,
+ r#"<li><a href="{url}">{name}</a></li>"#,
+ url = url,
+ name = call_data.display_name
+ );
+ });
+ write!(w, "</ul></div>");
+ }
+
+ write!(w, "</div></div></details>");
+ }
+
+ write!(w, "</div>");
+}
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
new file mode 100644
index 000000000..99cf42919
--- /dev/null
+++ b/src/librustdoc/html/render/print_item.rs
@@ -0,0 +1,1974 @@
+use clean::AttributesExt;
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir as hir;
+use rustc_hir::def::CtorKind;
+use rustc_hir::def_id::DefId;
+use rustc_middle::middle::stability;
+use rustc_middle::span_bug;
+use rustc_middle::ty::layout::LayoutError;
+use rustc_middle::ty::{Adt, TyCtxt};
+use rustc_span::hygiene::MacroKind;
+use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_target::abi::{Layout, Primitive, TagEncoding, Variants};
+use std::cmp::Ordering;
+use std::fmt;
+use std::rc::Rc;
+
+use super::{
+ collect_paths_for_type, document, ensure_trailing_slash, item_ty_to_section,
+ notable_traits_decl, render_assoc_item, render_assoc_items, render_attributes_in_code,
+ render_attributes_in_pre, render_impl, render_stability_since_raw, write_srclink,
+ AssocItemLink, Context, ImplRenderingParameters,
+};
+use crate::clean;
+use crate::config::ModuleSorting;
+use crate::formats::item_type::ItemType;
+use crate::formats::{AssocItemRender, Impl, RenderMode};
+use crate::html::escape::Escape;
+use crate::html::format::{
+ join_with_double_colon, print_abi_with_space, print_constness_with_space, print_where_clause,
+ Buffer, Ending, PrintWithSpace,
+};
+use crate::html::highlight;
+use crate::html::layout::Page;
+use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
+use crate::html::url_parts_builder::UrlPartsBuilder;
+
+use askama::Template;
+use itertools::Itertools;
+
+const ITEM_TABLE_OPEN: &str = "<div class=\"item-table\">";
+const ITEM_TABLE_CLOSE: &str = "</div>";
+const ITEM_TABLE_ROW_OPEN: &str = "<div class=\"item-row\">";
+const ITEM_TABLE_ROW_CLOSE: &str = "</div>";
+
+// A component in a `use` path, like `string` in std::string::ToString
+struct PathComponent {
+ path: String,
+ name: Symbol,
+}
+
+#[derive(Template)]
+#[template(path = "print_item.html")]
+struct ItemVars<'a> {
+ page: &'a Page<'a>,
+ static_root_path: &'a str,
+ typ: &'a str,
+ name: &'a str,
+ item_type: &'a str,
+ path_components: Vec<PathComponent>,
+ stability_since_raw: &'a str,
+ src_href: Option<&'a str>,
+}
+
+/// Calls `print_where_clause` and returns `true` if a `where` clause was generated.
+fn print_where_clause_and_check<'a, 'tcx: 'a>(
+ buffer: &mut Buffer,
+ gens: &'a clean::Generics,
+ cx: &'a Context<'tcx>,
+) -> bool {
+ let len_before = buffer.len();
+ write!(buffer, "{}", print_where_clause(gens, cx, 0, Ending::Newline));
+ len_before != buffer.len()
+}
+
+pub(super) fn print_item(
+ cx: &mut Context<'_>,
+ item: &clean::Item,
+ buf: &mut Buffer,
+ page: &Page<'_>,
+) {
+ debug_assert!(!item.is_stripped());
+ let typ = match *item.kind {
+ clean::ModuleItem(_) => {
+ if item.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!();
+ }
+ };
+ let mut stability_since_raw = Buffer::new();
+ render_stability_since_raw(
+ &mut stability_since_raw,
+ item.stable_since(cx.tcx()),
+ item.const_stability(cx.tcx()),
+ None,
+ None,
+ );
+ let stability_since_raw: String = stability_since_raw.into_inner();
+
+ // Write source tag
+ //
+ // When this item is part of a `crate use` in a downstream crate, the
+ // source 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.
+ let src_href =
+ if cx.include_sources && !item.is_primitive() { cx.src_href(item) } else { None };
+
+ let path_components = if item.is_primitive() || item.is_keyword() {
+ vec![]
+ } else {
+ let cur = &cx.current;
+ let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() };
+ cur.iter()
+ .enumerate()
+ .take(amt)
+ .map(|(i, component)| PathComponent {
+ path: "../".repeat(cur.len() - i - 1),
+ name: *component,
+ })
+ .collect()
+ };
+
+ let item_vars = ItemVars {
+ page,
+ static_root_path: page.get_static_root_path(),
+ typ,
+ name: item.name.as_ref().unwrap().as_str(),
+ item_type: &item.type_().to_string(),
+ path_components,
+ stability_since_raw: &stability_since_raw,
+ src_href: src_href.as_deref(),
+ };
+
+ item_vars.render_into(buf).unwrap();
+
+ 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!();
+ }
+ }
+}
+
+/// For large structs, enums, unions, etc, determine whether to hide their fields
+fn should_hide_fields(n_fields: usize) -> bool {
+ n_fields > 12
+}
+
+fn toggle_open(w: &mut Buffer, text: impl fmt::Display) {
+ write!(
+ w,
+ "<details class=\"rustdoc-toggle type-contents-toggle\">\
+ <summary class=\"hideme\">\
+ <span>Show {}</span>\
+ </summary>",
+ text
+ );
+}
+
+fn toggle_close(w: &mut Buffer) {
+ w.write_str("</details>");
+}
+
+fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) {
+ document(w, cx, item, None, HeadingOffset::H2);
+
+ let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>();
+
+ // the order of item types in the listing
+ fn reorder(ty: ItemType) -> u8 {
+ match ty {
+ ItemType::ExternCrate => 0,
+ ItemType::Import => 1,
+ ItemType::Primitive => 2,
+ ItemType::Module => 3,
+ ItemType::Macro => 4,
+ ItemType::Struct => 5,
+ ItemType::Enum => 6,
+ ItemType::Constant => 7,
+ ItemType::Static => 8,
+ ItemType::Trait => 9,
+ ItemType::Function => 10,
+ ItemType::Typedef => 12,
+ ItemType::Union => 13,
+ _ => 14 + ty as u8,
+ }
+ }
+
+ fn cmp(
+ i1: &clean::Item,
+ i2: &clean::Item,
+ idx1: usize,
+ idx2: usize,
+ tcx: TyCtxt<'_>,
+ ) -> Ordering {
+ let ty1 = i1.type_();
+ let ty2 = i2.type_();
+ if item_ty_to_section(ty1) != item_ty_to_section(ty2)
+ || (ty1 != ty2 && (ty1 == ItemType::ExternCrate || ty2 == ItemType::ExternCrate))
+ {
+ 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);
+ let rhs = i2.name.unwrap_or(kw::Empty);
+ compare_names(lhs.as_str(), rhs.as_str())
+ }
+
+ match cx.shared.module_sorting {
+ ModuleSorting::Alphabetical => {
+ indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2, cx.tcx()));
+ }
+ ModuleSorting::DeclarationOrder => {}
+ }
+ // This call is to remove re-export duplicates in cases such as:
+ //
+ // ```
+ // pub(crate) mod foo {
+ // pub(crate) mod bar {
+ // pub(crate) trait Double { fn foo(); }
+ // }
+ // }
+ //
+ // pub(crate) use foo::bar::*;
+ // pub(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].item_id,
+ if items[*i].name.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 last_section = None;
+
+ for &idx in &indices {
+ let myitem = &items[idx];
+ if myitem.is_stripped() {
+ continue;
+ }
+
+ let my_section = item_ty_to_section(myitem.type_());
+ if Some(my_section) != last_section {
+ if last_section.is_some() {
+ w.write_str(ITEM_TABLE_CLOSE);
+ }
+ last_section = Some(my_section);
+ write!(
+ w,
+ "<h2 id=\"{id}\" class=\"small-section-header\">\
+ <a href=\"#{id}\">{name}</a>\
+ </h2>{}",
+ ITEM_TABLE_OPEN,
+ id = cx.derive_id(my_section.id().to_owned()),
+ name = my_section.name(),
+ );
+ }
+
+ match *myitem.kind {
+ clean::ExternCrateItem { ref src } => {
+ use crate::html::format::anchor;
+
+ w.write_str(ITEM_TABLE_ROW_OPEN);
+ match *src {
+ Some(src) => write!(
+ w,
+ "<div class=\"item-left\"><code>{}extern crate {} as {};",
+ myitem.visibility.print_with_space(myitem.item_id, cx),
+ anchor(myitem.item_id.expect_def_id(), src, cx),
+ myitem.name.unwrap(),
+ ),
+ None => write!(
+ w,
+ "<div class=\"item-left\"><code>{}extern crate {};",
+ myitem.visibility.print_with_space(myitem.item_id, cx),
+ anchor(myitem.item_id.expect_def_id(), myitem.name.unwrap(), cx),
+ ),
+ }
+ w.write_str("</code></div>");
+ w.write_str(ITEM_TABLE_ROW_CLOSE);
+ }
+
+ clean::ImportItem(ref import) => {
+ let (stab, stab_tags) = if let Some(import_def_id) = import.source.did {
+ let ast_attrs = cx.tcx().get_attrs_unchecked(import_def_id);
+ let import_attrs = Box::new(clean::Attributes::from_ast(ast_attrs));
+
+ // Just need an item with the correct def_id and attrs
+ let import_item = clean::Item {
+ item_id: import_def_id.into(),
+ attrs: import_attrs,
+ cfg: ast_attrs.cfg(cx.tcx(), &cx.cache().hidden_cfg),
+ ..myitem.clone()
+ };
+
+ let stab = import_item.stability_class(cx.tcx());
+ let stab_tags = Some(extra_info_tags(&import_item, item, cx.tcx()));
+ (stab, stab_tags)
+ } else {
+ (None, None)
+ };
+
+ let add = if stab.is_some() { " " } else { "" };
+
+ w.write_str(ITEM_TABLE_ROW_OPEN);
+ let id = match import.kind {
+ clean::ImportKind::Simple(s) => {
+ format!(" id=\"{}\"", cx.derive_id(format!("reexport.{}", s)))
+ }
+ clean::ImportKind::Glob => String::new(),
+ };
+ write!(
+ w,
+ "<div class=\"item-left {stab}{add}import-item\"{id}>\
+ <code>{vis}{imp}</code>\
+ </div>\
+ <div class=\"item-right docblock-short\">{stab_tags}</div>",
+ stab = stab.unwrap_or_default(),
+ vis = myitem.visibility.print_with_space(myitem.item_id, cx),
+ imp = import.print(cx),
+ stab_tags = stab_tags.unwrap_or_default(),
+ );
+ w.write_str(ITEM_TABLE_ROW_CLOSE);
+ }
+
+ _ => {
+ if myitem.name.is_none() {
+ continue;
+ }
+
+ let unsafety_flag = match *myitem.kind {
+ clean::FunctionItem(_) | clean::ForeignFunctionItem(_)
+ if myitem.fn_header(cx.tcx()).unwrap().unsafety
+ == hir::Unsafety::Unsafe =>
+ {
+ "<a title=\"unsafe function\" href=\"#\"><sup>⚠</sup></a>"
+ }
+ _ => "",
+ };
+
+ let stab = myitem.stability_class(cx.tcx());
+ let add = if stab.is_some() { " " } else { "" };
+
+ let visibility_emoji = match myitem.visibility {
+ clean::Visibility::Restricted(_) => {
+ "<span title=\"Restricted Visibility\">&nbsp;🔒</span> "
+ }
+ _ => "",
+ };
+
+ let doc_value = myitem.doc_value().unwrap_or_default();
+ w.write_str(ITEM_TABLE_ROW_OPEN);
+ write!(
+ w,
+ "<div class=\"item-left {stab}{add}module-item\">\
+ <a class=\"{class}\" href=\"{href}\" title=\"{title}\">{name}</a>\
+ {visibility_emoji}\
+ {unsafety_flag}\
+ {stab_tags}\
+ </div>\
+ <div class=\"item-right docblock-short\">{docs}</div>",
+ name = myitem.name.unwrap(),
+ visibility_emoji = visibility_emoji,
+ stab_tags = extra_info_tags(myitem, item, cx.tcx()),
+ docs = MarkdownSummaryLine(&doc_value, &myitem.links(cx)).into_string(),
+ class = myitem.type_(),
+ add = add,
+ stab = stab.unwrap_or_default(),
+ 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::<Vec<_>>()
+ .join(" "),
+ );
+ w.write_str(ITEM_TABLE_ROW_CLOSE);
+ }
+ }
+ }
+
+ if last_section.is_some() {
+ w.write_str(ITEM_TABLE_CLOSE);
+ }
+}
+
+/// 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#"<span class="stab {}" title="{}">{}</span>"#, 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) {
+ 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.is_unstable() && s.feature != sym::rustc_private)
+ == Some(true)
+ {
+ tags += &tag_html("unstable", "", "Experimental");
+ }
+
+ let cfg = match (&item.cfg, parent.cfg.as_ref()) {
+ (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
+ (cfg, _) => cfg.as_deref().cloned(),
+ };
+
+ debug!("Portability {:?} - {:?} = {:?}", item.cfg, parent.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: &mut Context<'_>, it: &clean::Item, f: &clean::Function) {
+ let header = it.fn_header(cx.tcx()).expect("printing a function which isn't a function");
+ let constness = print_constness_with_space(&header.constness, it.const_stability(cx.tcx()));
+ let unsafety = header.unsafety.print_with_space();
+ let abi = print_abi_with_space(header.abi).to_string();
+ let asyncness = header.asyncness.print_with_space();
+ let visibility = it.visibility.print_with_space(it.item_id, cx).to_string();
+ let name = it.name.unwrap();
+
+ let generics_len = format!("{:#}", f.generics.print(cx)).len();
+ let header_len = "fn ".len()
+ + visibility.len()
+ + constness.len()
+ + asyncness.len()
+ + unsafety.len()
+ + abi.len()
+ + name.as_str().len()
+ + generics_len;
+
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "fn", |w| {
+ render_attributes_in_pre(w, it, "");
+ w.reserve(header_len);
+ write!(
+ w,
+ "{vis}{constness}{asyncness}{unsafety}{abi}fn \
+ {name}{generics}{decl}{notable_traits}{where_clause}",
+ vis = visibility,
+ constness = constness,
+ asyncness = asyncness,
+ unsafety = unsafety,
+ abi = abi,
+ name = name,
+ generics = f.generics.print(cx),
+ where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline),
+ decl = f.decl.full_print(header_len, 0, header.asyncness, cx),
+ notable_traits = notable_traits_decl(&f.decl, cx),
+ );
+ });
+ });
+ document(w, cx, it, None, HeadingOffset::H2)
+}
+
+fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) {
+ let bounds = bounds(&t.bounds, false, cx);
+ let required_types = t.items.iter().filter(|m| m.is_ty_associated_type()).collect::<Vec<_>>();
+ let provided_types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>();
+ let required_consts = t.items.iter().filter(|m| m.is_ty_associated_const()).collect::<Vec<_>>();
+ let provided_consts = t.items.iter().filter(|m| m.is_associated_const()).collect::<Vec<_>>();
+ let required_methods = t.items.iter().filter(|m| m.is_ty_method()).collect::<Vec<_>>();
+ let provided_methods = t.items.iter().filter(|m| m.is_method()).collect::<Vec<_>>();
+ let count_types = required_types.len() + provided_types.len();
+ let count_consts = required_consts.len() + provided_consts.len();
+ let count_methods = required_methods.len() + provided_methods.len();
+ let must_implement_one_of_functions =
+ cx.tcx().trait_def(t.def_id).must_implement_one_of.clone();
+
+ // Output the trait definition
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "trait", |w| {
+ render_attributes_in_pre(w, it, "");
+ write!(
+ w,
+ "{}{}{}trait {}{}{}",
+ it.visibility.print_with_space(it.item_id, cx),
+ t.unsafety(cx.tcx()).print_with_space(),
+ if t.is_auto(cx.tcx()) { "auto " } else { "" },
+ it.name.unwrap(),
+ t.generics.print(cx),
+ bounds
+ );
+
+ if !t.generics.where_predicates.is_empty() {
+ write!(w, "{}", print_where_clause(&t.generics, cx, 0, Ending::Newline));
+ } 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");
+ let mut toggle = false;
+
+ // If there are too many associated types, hide _everything_
+ if should_hide_fields(count_types) {
+ toggle = true;
+ toggle_open(
+ w,
+ format_args!(
+ "{} associated items",
+ count_types + count_consts + count_methods
+ ),
+ );
+ }
+ for types in [&required_types, &provided_types] {
+ for t in types {
+ render_assoc_item(
+ w,
+ t,
+ AssocItemLink::Anchor(None),
+ ItemType::Trait,
+ cx,
+ RenderMode::Normal,
+ );
+ w.write_str(";\n");
+ }
+ }
+ // If there are too many associated constants, hide everything after them
+ // We also do this if the types + consts is large because otherwise we could
+ // render a bunch of types and _then_ a bunch of consts just because both were
+ // _just_ under the limit
+ if !toggle && should_hide_fields(count_types + count_consts) {
+ toggle = true;
+ toggle_open(
+ w,
+ format_args!(
+ "{} associated constant{} and {} method{}",
+ count_consts,
+ pluralize(count_consts),
+ count_methods,
+ pluralize(count_methods),
+ ),
+ );
+ }
+ if count_types != 0 && (count_consts != 0 || count_methods != 0) {
+ w.write_str("\n");
+ }
+ for consts in [&required_consts, &provided_consts] {
+ for c in consts {
+ render_assoc_item(
+ w,
+ c,
+ AssocItemLink::Anchor(None),
+ ItemType::Trait,
+ cx,
+ RenderMode::Normal,
+ );
+ w.write_str(";\n");
+ }
+ }
+ if !toggle && should_hide_fields(count_methods) {
+ toggle = true;
+ toggle_open(w, format_args!("{} methods", count_methods));
+ }
+ if count_consts != 0 && count_methods != 0 {
+ w.write_str("\n");
+ }
+ for (pos, m) in required_methods.iter().enumerate() {
+ render_assoc_item(
+ w,
+ m,
+ AssocItemLink::Anchor(None),
+ ItemType::Trait,
+ cx,
+ RenderMode::Normal,
+ );
+ w.write_str(";\n");
+
+ if pos < required_methods.len() - 1 {
+ w.write_str("<span class=\"item-spacer\"></span>");
+ }
+ }
+ if !required_methods.is_empty() && !provided_methods.is_empty() {
+ w.write_str("\n");
+ }
+ for (pos, m) in provided_methods.iter().enumerate() {
+ render_assoc_item(
+ w,
+ m,
+ AssocItemLink::Anchor(None),
+ ItemType::Trait,
+ cx,
+ RenderMode::Normal,
+ );
+ 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_methods.len() - 1 {
+ w.write_str("<span class=\"item-spacer\"></span>");
+ }
+ }
+ if toggle {
+ toggle_close(w);
+ }
+ w.write_str("}");
+ }
+ });
+ });
+
+ // Trait documentation
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
+ write!(
+ w,
+ "<h2 id=\"{0}\" class=\"small-section-header\">\
+ {1}<a href=\"#{0}\" class=\"anchor\"></a>\
+ </h2>{2}",
+ id, title, extra_content
+ )
+ }
+
+ fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::Item) {
+ let name = m.name.unwrap();
+ info!("Documenting {} on {:?}", name, t.name);
+ let item_type = m.type_();
+ let id = cx.derive_id(format!("{}.{}", item_type, name));
+ let mut content = Buffer::empty_from(w);
+ document(&mut content, cx, m, Some(t), HeadingOffset::H5);
+ let toggled = !content.is_empty();
+ if toggled {
+ write!(w, "<details class=\"rustdoc-toggle\" open><summary>");
+ }
+ write!(w, "<div id=\"{}\" class=\"method has-srclink\">", id);
+ write!(w, "<div class=\"rightside\">");
+
+ let has_stability = render_stability_since(w, m, t, cx.tcx());
+ if has_stability {
+ w.write_str(" · ");
+ }
+ write_srclink(cx, m, w);
+ write!(w, "</div>");
+ write!(w, "<h4 class=\"code-header\">");
+ render_assoc_item(
+ w,
+ m,
+ AssocItemLink::Anchor(Some(&id)),
+ ItemType::Impl,
+ cx,
+ RenderMode::Normal,
+ );
+ w.write_str("</h4>");
+ w.write_str("</div>");
+ if toggled {
+ write!(w, "</summary>");
+ w.push_buffer(content);
+ write!(w, "</details>");
+ }
+ }
+
+ if !required_types.is_empty() {
+ write_small_section_header(
+ w,
+ "required-associated-types",
+ "Required Associated Types",
+ "<div class=\"methods\">",
+ );
+ for t in required_types {
+ trait_item(w, cx, t, it);
+ }
+ w.write_str("</div>");
+ }
+ if !provided_types.is_empty() {
+ write_small_section_header(
+ w,
+ "provided-associated-types",
+ "Provided Associated Types",
+ "<div class=\"methods\">",
+ );
+ for t in provided_types {
+ trait_item(w, cx, t, it);
+ }
+ w.write_str("</div>");
+ }
+
+ if !required_consts.is_empty() {
+ write_small_section_header(
+ w,
+ "required-associated-consts",
+ "Required Associated Constants",
+ "<div class=\"methods\">",
+ );
+ for t in required_consts {
+ trait_item(w, cx, t, it);
+ }
+ w.write_str("</div>");
+ }
+ if !provided_consts.is_empty() {
+ write_small_section_header(
+ w,
+ "provided-associated-consts",
+ "Provided Associated Constants",
+ "<div class=\"methods\">",
+ );
+ for t in provided_consts {
+ trait_item(w, cx, t, it);
+ }
+ w.write_str("</div>");
+ }
+
+ // Output the documentation for each function individually
+ if !required_methods.is_empty() || must_implement_one_of_functions.is_some() {
+ write_small_section_header(
+ w,
+ "required-methods",
+ "Required Methods",
+ "<div class=\"methods\">",
+ );
+
+ if let Some(list) = must_implement_one_of_functions.as_deref() {
+ write!(
+ w,
+ "<div class=\"stab must_implement\">At least one of the `{}` methods is required.</div>",
+ list.iter().join("`, `")
+ );
+ }
+
+ for m in required_methods {
+ trait_item(w, cx, m, it);
+ }
+ w.write_str("</div>");
+ }
+ if !provided_methods.is_empty() {
+ write_small_section_header(
+ w,
+ "provided-methods",
+ "Provided Methods",
+ "<div class=\"methods\">",
+ );
+ for m in provided_methods {
+ trait_item(w, cx, m, it);
+ }
+ w.write_str("</div>");
+ }
+
+ // If there are methods directly on this trait object, render them here.
+ render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All);
+
+ let cloned_shared = Rc::clone(&cx.shared);
+ let cache = &cloned_shared.cache;
+ let mut extern_crates = FxHashSet::default();
+ if let Some(implementors) = cache.implementors.get(&it.item_id.expect_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<Symbol, (DefId, bool)> = FxHashMap::default();
+ for implementor in implementors {
+ if let Some(did) = implementor.inner_impl().for_.without_borrowed_ref().def_id(cache) &&
+ !did.is_local() {
+ extern_crates.insert(did.krate);
+ }
+ match implementor.inner_impl().for_.without_borrowed_ref() {
+ clean::Type::Path { ref path } if !path.is_assoc_ty() => {
+ let did = path.def_id();
+ 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::<Vec<_>, _>(|i| i.is_on_local_type(cx));
+
+ let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) =
+ local.iter().partition(|i| i.inner_impl().kind.is_auto());
+
+ synthetic.sort_by(|a, b| compare_impl(a, b, cx));
+ concrete.sort_by(|a, b| compare_impl(a, b, cx));
+
+ if !foreign.is_empty() {
+ write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
+
+ for implementor in foreign {
+ let provided_methods = implementor.inner_impl().provided_trait_methods(cx.tcx());
+ let assoc_link =
+ AssocItemLink::GotoSource(implementor.impl_item.item_id, &provided_methods);
+ render_impl(
+ w,
+ cx,
+ implementor,
+ it,
+ assoc_link,
+ RenderMode::Normal,
+ None,
+ &[],
+ ImplRenderingParameters {
+ show_def_docs: false,
+ show_default_items: false,
+ show_non_assoc_items: true,
+ toggle_open_by_default: false,
+ },
+ );
+ }
+ }
+
+ write_small_section_header(
+ w,
+ "implementors",
+ "Implementors",
+ "<div class=\"item-list\" id=\"implementors-list\">",
+ );
+ for implementor in concrete {
+ render_implementor(cx, implementor, it, w, &implementor_dups, &[]);
+ }
+ w.write_str("</div>");
+
+ if t.is_auto(cx.tcx()) {
+ write_small_section_header(
+ w,
+ "synthetic-implementors",
+ "Auto implementors",
+ "<div class=\"item-list\" id=\"synthetic-implementors-list\">",
+ );
+ for implementor in synthetic {
+ render_implementor(
+ cx,
+ implementor,
+ it,
+ w,
+ &implementor_dups,
+ &collect_paths_for_type(implementor.inner_impl().for_.clone(), cache),
+ );
+ }
+ w.write_str("</div>");
+ }
+ } else {
+ // even without any implementations to write in, we still want the heading and list, so the
+ // implementors javascript file pulled in below has somewhere to write the impls into
+ write_small_section_header(
+ w,
+ "implementors",
+ "Implementors",
+ "<div class=\"item-list\" id=\"implementors-list\"></div>",
+ );
+
+ if t.is_auto(cx.tcx()) {
+ write_small_section_header(
+ w,
+ "synthetic-implementors",
+ "Auto implementors",
+ "<div class=\"item-list\" id=\"synthetic-implementors-list\"></div>",
+ );
+ }
+ }
+
+ // Include implementors in crates that depend on the current crate.
+ //
+ // This is complicated by the way rustdoc is invoked, which is basically
+ // the same way rustc is invoked: it gets called, one at a time, for each
+ // crate. When building the rustdocs for the current crate, rustdoc can
+ // see crate metadata for its dependencies, but cannot see metadata for its
+ // dependents.
+ //
+ // To make this work, we generate a "hook" at this stage, and our
+ // dependents can "plug in" to it when they build. For simplicity's sake,
+ // it's [JSONP]: a JavaScript file with the data we need (and can parse),
+ // surrounded by a tiny wrapper that the Rust side ignores, but allows the
+ // JavaScript side to include without having to worry about Same Origin
+ // Policy. The code for *that* is in `write_shared.rs`.
+ //
+ // This is further complicated by `#[doc(inline)]`. We want all copies
+ // of an inlined trait to reference the same JS file, to address complex
+ // dependency graphs like this one (lower crates depend on higher crates):
+ //
+ // ```text
+ // --------------------------------------------
+ // | crate A: trait Foo |
+ // --------------------------------------------
+ // | |
+ // -------------------------------- |
+ // | crate B: impl A::Foo for Bar | |
+ // -------------------------------- |
+ // | |
+ // ---------------------------------------------
+ // | crate C: #[doc(inline)] use A::Foo as Baz |
+ // | impl Baz for Quux |
+ // ---------------------------------------------
+ // ```
+ //
+ // Basically, we want `C::Baz` and `A::Foo` to show the same set of
+ // impls, which is easier if they both treat `/implementors/A/trait.Foo.js`
+ // as the Single Source of Truth.
+ //
+ // We also want the `impl Baz for Quux` to be written to
+ // `trait.Foo.js`. However, when we generate plain HTML for `C::Baz`,
+ // we're going to want to generate plain HTML for `impl Baz for Quux` too,
+ // because that'll load faster, and it's better for SEO. And we don't want
+ // the same impl to show up twice on the same page.
+ //
+ // To make this work, the implementors JS file has a structure kinda
+ // like this:
+ //
+ // ```js
+ // JSONP({
+ // "B": {"impl A::Foo for Bar"},
+ // "C": {"impl Baz for Quux"},
+ // });
+ // ```
+ //
+ // First of all, this means we can rebuild a crate, and it'll replace its own
+ // data if something changes. That is, `rustdoc` is idempotent. The other
+ // advantage is that we can list the crates that get included in the HTML,
+ // and ignore them when doing the JavaScript-based part of rendering.
+ // So C's HTML will have something like this:
+ //
+ // ```html
+ // <script type="text/javascript" src="/implementors/A/trait.Foo.js"
+ // data-ignore-extern-crates="A,B" async></script>
+ // ```
+ //
+ // And, when the JS runs, anything in data-ignore-extern-crates is known
+ // to already be in the HTML, and will be ignored.
+ //
+ // [JSONP]: https://en.wikipedia.org/wiki/JSONP
+ let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..")
+ .take(cx.current.len())
+ .chain(std::iter::once("implementors"))
+ .collect();
+ if let Some(did) = it.item_id.as_def_id() &&
+ let get_extern = { || cache.external_paths.get(&did).map(|s| s.0.clone()) } &&
+ let Some(fqp) = cache.exact_paths.get(&did).cloned().or_else(get_extern) {
+ js_src_path.extend(fqp[..fqp.len() - 1].iter().copied());
+ js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), fqp.last().unwrap()));
+ } else {
+ js_src_path.extend(cx.current.iter().copied());
+ js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), it.name.unwrap()));
+ }
+ let extern_crates = extern_crates
+ .into_iter()
+ .map(|cnum| cx.shared.tcx.crate_name(cnum).to_string())
+ .collect::<Vec<_>>()
+ .join(",");
+ write!(
+ w,
+ "<script type=\"text/javascript\" src=\"{src}\" data-ignore-extern-crates=\"{extern_crates}\" async></script>",
+ src = js_src_path.finish(),
+ );
+}
+
+fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TraitAlias) {
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "trait-alias", |w| {
+ render_attributes_in_pre(w, it, "");
+ write!(
+ w,
+ "trait {}{}{} = {};",
+ it.name.unwrap(),
+ t.generics.print(cx),
+ print_where_clause(&t.generics, cx, 0, Ending::Newline),
+ bounds(&t.bounds, true, cx)
+ );
+ });
+ });
+
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ // 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.item_id.expect_def_id(), AssocItemRender::All)
+}
+
+fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) {
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "opaque", |w| {
+ render_attributes_in_pre(w, it, "");
+ write!(
+ w,
+ "type {}{}{where_clause} = impl {bounds};",
+ it.name.unwrap(),
+ t.generics.print(cx),
+ where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
+ bounds = bounds(&t.bounds, false, cx),
+ );
+ });
+ });
+
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ // 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.item_id.expect_def_id(), AssocItemRender::All)
+}
+
+fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) {
+ fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) {
+ wrap_item(w, "typedef", |w| {
+ render_attributes_in_pre(w, it, "");
+ write!(w, "{}", it.visibility.print_with_space(it.item_id, cx));
+ write!(
+ w,
+ "type {}{}{where_clause} = {type_};",
+ it.name.unwrap(),
+ t.generics.print(cx),
+ where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
+ type_ = t.type_.print(cx),
+ );
+ });
+ }
+
+ wrap_into_docblock(w, |w| write_content(w, cx, it, t));
+
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ let def_id = it.item_id.expect_def_id();
+ // 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, def_id, AssocItemRender::All);
+ document_type_layout(w, cx, def_id);
+}
+
+fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) {
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "union", |w| {
+ render_attributes_in_pre(w, it, "");
+ render_union(w, it, Some(&s.generics), &s.fields, "", cx);
+ });
+ });
+
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ 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,
+ "<h2 id=\"fields\" class=\"fields small-section-header\">\
+ Fields<a href=\"#fields\" class=\"anchor\"></a>\
+ </h2>"
+ );
+ for (field, ty) in fields {
+ let name = field.name.expect("union field name");
+ let id = format!("{}.{}", ItemType::StructField, name);
+ write!(
+ w,
+ "<span id=\"{id}\" class=\"{shortty} small-section-header\">\
+ <a href=\"#{id}\" class=\"anchor field\"></a>\
+ <code>{name}: {ty}</code>\
+ </span>",
+ id = id,
+ name = name,
+ shortty = ItemType::StructField,
+ ty = ty.print(cx),
+ );
+ if let Some(stability_class) = field.stability_class(cx.tcx()) {
+ write!(w, "<span class=\"stab {stab}\"></span>", stab = stability_class);
+ }
+ document(w, cx, field, Some(it), HeadingOffset::H3);
+ }
+ }
+ let def_id = it.item_id.expect_def_id();
+ render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
+ document_type_layout(w, cx, def_id);
+}
+
+fn print_tuple_struct_fields(w: &mut Buffer, cx: &Context<'_>, s: &[clean::Item]) {
+ for (i, ty) in s.iter().enumerate() {
+ if i > 0 {
+ w.write_str(",&nbsp;");
+ }
+ match *ty.kind {
+ clean::StrippedItem(box clean::StructFieldItem(_)) => w.write_str("_"),
+ clean::StructFieldItem(ref ty) => write!(w, "{}", ty.print(cx)),
+ _ => unreachable!(),
+ }
+ }
+}
+
+fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) {
+ let count_variants = e.variants().count();
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "enum", |w| {
+ render_attributes_in_pre(w, it, "");
+ write!(
+ w,
+ "{}enum {}{}",
+ it.visibility.print_with_space(it.item_id, cx),
+ it.name.unwrap(),
+ e.generics.print(cx),
+ );
+ if !print_where_clause_and_check(w, &e.generics, cx) {
+ // If there wasn't a `where` clause, we add a whitespace.
+ w.write_str(" ");
+ }
+
+ let variants_stripped = e.has_stripped_entries();
+ if count_variants == 0 && !variants_stripped {
+ w.write_str("{}");
+ } else {
+ w.write_str("{\n");
+ let toggle = should_hide_fields(count_variants);
+ if toggle {
+ toggle_open(w, format_args!("{} variants", count_variants));
+ }
+ for v in e.variants() {
+ w.write_str(" ");
+ let name = v.name.unwrap();
+ match *v.kind {
+ clean::VariantItem(ref var) => match var {
+ clean::Variant::CLike => write!(w, "{}", name),
+ clean::Variant::Tuple(ref s) => {
+ write!(w, "{}(", name);
+ print_tuple_struct_fields(w, cx, s);
+ 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 variants_stripped {
+ w.write_str(" // some variants omitted\n");
+ }
+ if toggle {
+ toggle_close(w);
+ }
+ w.write_str("}");
+ }
+ });
+ });
+
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ if count_variants != 0 {
+ write!(
+ w,
+ "<h2 id=\"variants\" class=\"variants small-section-header\">\
+ Variants{}<a href=\"#variants\" class=\"anchor\"></a>\
+ </h2>",
+ 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.unwrap()));
+ write!(
+ w,
+ "<h3 id=\"{id}\" class=\"variant small-section-header\">\
+ <a href=\"#{id}\" class=\"anchor field\"></a>\
+ <code>{name}",
+ id = id,
+ name = variant.name.unwrap()
+ );
+ if let clean::VariantItem(clean::Variant::Tuple(ref s)) = *variant.kind {
+ w.write_str("(");
+ print_tuple_struct_fields(w, cx, s);
+ w.write_str(")");
+ }
+ w.write_str("</code>");
+ render_stability_since(w, variant, it, cx.tcx());
+ w.write_str("</h3>");
+
+ use crate::clean::Variant;
+
+ let heading_and_fields = match &*variant.kind {
+ clean::VariantItem(Variant::Struct(s)) => Some(("Fields", &s.fields)),
+ // Documentation on tuple variant fields is rare, so to reduce noise we only emit
+ // the section if at least one field is documented.
+ clean::VariantItem(Variant::Tuple(fields))
+ if fields.iter().any(|f| f.doc_value().is_some()) =>
+ {
+ Some(("Tuple Fields", fields))
+ }
+ _ => None,
+ };
+
+ if let Some((heading, fields)) = heading_and_fields {
+ let variant_id =
+ cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap()));
+ write!(w, "<div class=\"sub-variant\" id=\"{id}\">", id = variant_id);
+ write!(w, "<h4>{heading}</h4>", heading = heading);
+ document_non_exhaustive(w, variant);
+ for field in fields {
+ match *field.kind {
+ clean::StrippedItem(box clean::StructFieldItem(_)) => {}
+ clean::StructFieldItem(ref ty) => {
+ let id = cx.derive_id(format!(
+ "variant.{}.field.{}",
+ variant.name.unwrap(),
+ field.name.unwrap()
+ ));
+ write!(
+ w,
+ "<div class=\"sub-variant-field\">\
+ <span id=\"{id}\" class=\"variant small-section-header\">\
+ <a href=\"#{id}\" class=\"anchor field\"></a>\
+ <code>{f}:&nbsp;{t}</code>\
+ </span>",
+ id = id,
+ f = field.name.unwrap(),
+ t = ty.print(cx)
+ );
+ document(w, cx, field, Some(variant), HeadingOffset::H5);
+ write!(w, "</div>");
+ }
+ _ => unreachable!(),
+ }
+ }
+ w.write_str("</div>");
+ }
+
+ document(w, cx, variant, Some(it), HeadingOffset::H4);
+ }
+ }
+ let def_id = it.item_id.expect_def_id();
+ render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
+ document_type_layout(w, cx, def_id);
+}
+
+fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) {
+ wrap_into_docblock(w, |w| {
+ highlight::render_with_highlighting(
+ &t.source,
+ w,
+ Some("macro"),
+ None,
+ None,
+ it.span(cx.tcx()).inner().edition(),
+ None,
+ None,
+ None,
+ );
+ });
+ document(w, cx, it, None, HeadingOffset::H2)
+}
+
+fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
+ wrap_into_docblock(w, |w| {
+ let name = it.name.expect("proc-macros always have names");
+ match m.kind {
+ MacroKind::Bang => {
+ wrap_item(w, "macro", |w| {
+ write!(w, "{}!() {{ /* proc-macro */ }}", name);
+ });
+ }
+ MacroKind::Attr => {
+ wrap_item(w, "attr", |w| {
+ write!(w, "#[{}]", name);
+ });
+ }
+ MacroKind::Derive => {
+ wrap_item(w, "derive", |w| {
+ 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");
+ }
+ });
+ }
+ }
+ });
+ document(w, cx, it, None, HeadingOffset::H2)
+}
+
+fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
+ document(w, cx, it, None, HeadingOffset::H2);
+ render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
+}
+
+fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) {
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "const", |w| {
+ render_attributes_in_code(w, it);
+
+ write!(
+ w,
+ "{vis}const {name}: {typ}",
+ vis = it.visibility.print_with_space(it.item_id, cx),
+ name = it.name.unwrap(),
+ typ = c.type_.print(cx),
+ );
+
+ // FIXME: The code below now prints
+ // ` = _; // 100i32`
+ // if the expression is
+ // `50 + 50`
+ // which looks just wrong.
+ // Should we print
+ // ` = 100i32;`
+ // instead?
+
+ let value = c.value(cx.tcx());
+ let is_literal = c.is_literal(cx.tcx());
+ let expr = c.expr(cx.tcx());
+ if value.is_some() || is_literal {
+ write!(w, " = {expr};", expr = Escape(&expr));
+ } else {
+ w.write_str(";");
+ }
+
+ if !is_literal {
+ if let Some(value) = &value {
+ let value_lowercase = value.to_lowercase();
+ let expr_lowercase = expr.to_lowercase();
+
+ if value_lowercase != expr_lowercase
+ && value_lowercase.trim_end_matches("i32") != expr_lowercase
+ {
+ write!(w, " // {value}", value = Escape(value));
+ }
+ }
+ }
+ });
+ });
+
+ document(w, cx, it, None, HeadingOffset::H2)
+}
+
+fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) {
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "struct", |w| {
+ render_attributes_in_code(w, it);
+ render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
+ });
+ });
+
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ 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 | CtorKind::Fn = s.struct_type {
+ if fields.peek().is_some() {
+ write!(
+ w,
+ "<h2 id=\"fields\" class=\"fields small-section-header\">\
+ {}{}<a href=\"#fields\" class=\"anchor\"></a>\
+ </h2>",
+ if let CtorKind::Fictive = s.struct_type { "Fields" } else { "Tuple Fields" },
+ document_non_exhaustive_header(it)
+ );
+ document_non_exhaustive(w, it);
+ for (index, (field, ty)) in fields.enumerate() {
+ let field_name =
+ field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string());
+ let id = cx.derive_id(format!("{}.{}", ItemType::StructField, field_name));
+ write!(
+ w,
+ "<span id=\"{id}\" class=\"{item_type} small-section-header\">\
+ <a href=\"#{id}\" class=\"anchor field\"></a>\
+ <code>{name}: {ty}</code>\
+ </span>",
+ item_type = ItemType::StructField,
+ id = id,
+ name = field_name,
+ ty = ty.print(cx)
+ );
+ document(w, cx, field, Some(it), HeadingOffset::H3);
+ }
+ }
+ }
+ let def_id = it.item_id.expect_def_id();
+ render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
+ document_type_layout(w, cx, def_id);
+}
+
+fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "static", |w| {
+ render_attributes_in_code(w, it);
+ write!(
+ w,
+ "{vis}static {mutability}{name}: {typ}",
+ vis = it.visibility.print_with_space(it.item_id, cx),
+ mutability = s.mutability.print_with_space(),
+ name = it.name.unwrap(),
+ typ = s.type_.print(cx)
+ );
+ });
+ });
+ document(w, cx, it, None, HeadingOffset::H2)
+}
+
+fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
+ wrap_into_docblock(w, |w| {
+ wrap_item(w, "foreigntype", |w| {
+ w.write_str("extern {\n");
+ render_attributes_in_code(w, it);
+ write!(
+ w,
+ " {}type {};\n}}",
+ it.visibility.print_with_space(it.item_id, cx),
+ it.name.unwrap(),
+ );
+ });
+ });
+
+ document(w, cx, it, None, HeadingOffset::H2);
+
+ render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
+}
+
+fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
+ document(w, cx, it, None, HeadingOffset::H2)
+}
+
+/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order).
+pub(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::<u64>(), rb.parse::<u64>()) {
+ 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 = join_with_double_colon(&cx.current);
+ 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, cx: &Context<'_>) -> 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(cx).to_string());
+ }
+ }
+ bounds
+}
+
+fn wrap_into_docblock<F>(w: &mut Buffer, f: F)
+where
+ F: FnOnce(&mut Buffer),
+{
+ w.write_str("<div class=\"docblock item-decl\">");
+ f(w);
+ w.write_str("</div>")
+}
+
+fn wrap_item<F>(w: &mut Buffer, item_name: &str, f: F)
+where
+ F: FnOnce(&mut Buffer),
+{
+ w.write_fmt(format_args!("<pre class=\"rust {}\"><code>", item_name));
+ f(w);
+ w.write_str("</code></pre>");
+}
+
+fn render_stability_since(
+ w: &mut Buffer,
+ item: &clean::Item,
+ containing_item: &clean::Item,
+ tcx: TyCtxt<'_>,
+) -> bool {
+ render_stability_since_raw(
+ w,
+ item.stable_since(tcx),
+ item.const_stability(tcx),
+ containing_item.stable_since(tcx),
+ containing_item.const_stable_since(tcx),
+ )
+}
+
+fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cx: &Context<'_>) -> Ordering {
+ let lhss = format!("{}", lhs.inner_impl().print(false, cx));
+ let rhss = format!("{}", rhs.inner_impl().print(false, cx));
+
+ // lhs and rhs are formatted as HTML, which may be unnecessary
+ compare_names(&lhss, &rhss)
+}
+
+fn render_implementor(
+ cx: &mut Context<'_>,
+ implementor: &Impl,
+ trait_: &clean::Item,
+ w: &mut Buffer,
+ implementor_dups: &FxHashMap<Symbol, (DefId, bool)>,
+ aliases: &[String],
+) {
+ // If there's already another implementor that has the same abridged name, use the
+ // full path, for example in `std::iter::ExactSizeIterator`
+ let use_absolute = match implementor.inner_impl().for_ {
+ clean::Type::Path { ref path, .. }
+ | clean::BorrowedRef { type_: box clean::Type::Path { ref path, .. }, .. }
+ if !path.is_assoc_ty() =>
+ {
+ implementor_dups[&path.last()].1
+ }
+ _ => false,
+ };
+ render_impl(
+ w,
+ cx,
+ implementor,
+ trait_,
+ AssocItemLink::Anchor(None),
+ RenderMode::Normal,
+ Some(use_absolute),
+ aliases,
+ ImplRenderingParameters {
+ show_def_docs: false,
+ show_default_items: false,
+ show_non_assoc_items: false,
+ toggle_open_by_default: false,
+ },
+ );
+}
+
+fn render_union(
+ w: &mut Buffer,
+ it: &clean::Item,
+ g: Option<&clean::Generics>,
+ fields: &[clean::Item],
+ tab: &str,
+ cx: &Context<'_>,
+) {
+ write!(w, "{}union {}", it.visibility.print_with_space(it.item_id, cx), it.name.unwrap(),);
+
+ let where_displayed = g
+ .map(|g| {
+ write!(w, "{}", g.print(cx));
+ print_where_clause_and_check(w, g, cx)
+ })
+ .unwrap_or(false);
+
+ // If there wasn't a `where` clause, we add a whitespace.
+ if !where_displayed {
+ w.write_str(" ");
+ }
+
+ write!(w, "{{\n{}", tab);
+ let count_fields =
+ fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count();
+ let toggle = should_hide_fields(count_fields);
+ if toggle {
+ toggle_open(w, format_args!("{} fields", count_fields));
+ }
+
+ for field in fields {
+ if let clean::StructFieldItem(ref ty) = *field.kind {
+ write!(
+ w,
+ " {}{}: {},\n{}",
+ field.visibility.print_with_space(field.item_id, cx),
+ field.name.unwrap(),
+ ty.print(cx),
+ tab
+ );
+ }
+ }
+
+ if it.has_stripped_entries().unwrap() {
+ write!(w, " /* private fields */\n{}", tab);
+ }
+ if toggle {
+ toggle_close(w);
+ }
+ 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(it.item_id, cx),
+ if structhead { "struct " } else { "" },
+ it.name.unwrap()
+ );
+ if let Some(g) = g {
+ write!(w, "{}", g.print(cx))
+ }
+ match ty {
+ CtorKind::Fictive => {
+ let where_diplayed = g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false);
+
+ // If there wasn't a `where` clause, we add a whitespace.
+ if !where_diplayed {
+ w.write_str(" {");
+ } else {
+ w.write_str("{");
+ }
+ let count_fields =
+ fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count();
+ let has_visible_fields = count_fields > 0;
+ let toggle = should_hide_fields(count_fields);
+ if toggle {
+ toggle_open(w, format_args!("{} fields", count_fields));
+ }
+ for field in fields {
+ if let clean::StructFieldItem(ref ty) = *field.kind {
+ write!(
+ w,
+ "\n{} {}{}: {},",
+ tab,
+ field.visibility.print_with_space(field.item_id, cx),
+ field.name.unwrap(),
+ ty.print(cx),
+ );
+ }
+ }
+
+ if has_visible_fields {
+ if it.has_stripped_entries().unwrap() {
+ write!(w, "\n{} /* private fields */", tab);
+ }
+ write!(w, "\n{}", tab);
+ } else if it.has_stripped_entries().unwrap() {
+ write!(w, " /* private fields */ ");
+ }
+ if toggle {
+ toggle_close(w);
+ }
+ 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(field.item_id, cx),
+ ty.print(cx),
+ )
+ }
+ _ => unreachable!(),
+ }
+ }
+ w.write_str(")");
+ if let Some(g) = g {
+ write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline));
+ }
+ // We only want a ";" when we are displaying a tuple struct, not a variant tuple struct.
+ if structhead {
+ w.write_str(";");
+ }
+ }
+ CtorKind::Const => {
+ // Needed for PhantomData.
+ if let Some(g) = g {
+ write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline));
+ }
+ 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,
+ "<details class=\"rustdoc-toggle non-exhaustive\">\
+ <summary class=\"hideme\"><span>{}</span></summary>\
+ <div class=\"docblock\">",
+ {
+ if item.is_struct() {
+ "This struct is marked as non-exhaustive"
+ } else if item.is_enum() {
+ "This enum is marked as non-exhaustive"
+ } else if item.is_variant() {
+ "This variant is marked as non-exhaustive"
+ } else {
+ "This type is marked as non-exhaustive"
+ }
+ }
+ );
+
+ 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 <code>Struct { .. }</code> syntax; cannot be \
+ matched against without a wildcard <code>..</code>; and \
+ struct update syntax will not work.",
+ );
+ } else if item.is_enum() {
+ 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("</div></details>");
+ }
+}
+
+fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
+ fn write_size_of_layout(w: &mut Buffer, layout: Layout<'_>, tag_size: u64) {
+ if layout.abi().is_unsized() {
+ write!(w, "(unsized)");
+ } else {
+ let bytes = layout.size().bytes() - tag_size;
+ write!(w, "{size} byte{pl}", size = bytes, pl = if bytes == 1 { "" } else { "s" },);
+ }
+ }
+
+ if !cx.shared.show_type_layout {
+ return;
+ }
+
+ writeln!(
+ w,
+ "<h2 id=\"layout\" class=\"small-section-header\"> \
+ Layout<a href=\"#layout\" class=\"anchor\"></a></h2>"
+ );
+ writeln!(w, "<div class=\"docblock\">");
+
+ let tcx = cx.tcx();
+ let param_env = tcx.param_env(ty_def_id);
+ let ty = tcx.type_of(ty_def_id);
+ match tcx.layout_of(param_env.and(ty)) {
+ Ok(ty_layout) => {
+ writeln!(
+ w,
+ "<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \
+ <strong>completely unstable</strong> and may even differ between compilations. \
+ The only exception is types with certain <code>repr(...)</code> attributes. \
+ Please see the Rust Reference’s \
+ <a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \
+ chapter for details on type layout guarantees.</p></div>"
+ );
+ w.write_str("<p><strong>Size:</strong> ");
+ write_size_of_layout(w, ty_layout.layout, 0);
+ writeln!(w, "</p>");
+ if let Variants::Multiple { variants, tag, tag_encoding, .. } =
+ &ty_layout.layout.variants()
+ {
+ if !variants.is_empty() {
+ w.write_str(
+ "<p><strong>Size for each variant:</strong></p>\
+ <ul>",
+ );
+
+ let Adt(adt, _) = ty_layout.ty.kind() else {
+ span_bug!(tcx.def_span(ty_def_id), "not an adt")
+ };
+
+ let tag_size = if let TagEncoding::Niche { .. } = tag_encoding {
+ 0
+ } else if let Primitive::Int(i, _) = tag.primitive() {
+ i.size().bytes()
+ } else {
+ span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int")
+ };
+
+ for (index, layout) in variants.iter_enumerated() {
+ let name = adt.variant(index).name;
+ write!(w, "<li><code>{name}</code>: ", name = name);
+ write_size_of_layout(w, *layout, tag_size);
+ writeln!(w, "</li>");
+ }
+ w.write_str("</ul>");
+ }
+ }
+ }
+ // This kind of layout error can occur with valid code, e.g. if you try to
+ // get the layout of a generic type such as `Vec<T>`.
+ Err(LayoutError::Unknown(_)) => {
+ writeln!(
+ w,
+ "<p><strong>Note:</strong> Unable to compute type layout, \
+ possibly due to this type having generic parameters. \
+ Layout can only be computed for concrete, fully-instantiated types.</p>"
+ );
+ }
+ // This kind of error probably can't happen with valid code, but we don't
+ // want to panic and prevent the docs from building, so we just let the
+ // user know that we couldn't compute the layout.
+ Err(LayoutError::SizeOverflow(_)) => {
+ writeln!(
+ w,
+ "<p><strong>Note:</strong> Encountered an error during type layout; \
+ the type was too big.</p>"
+ );
+ }
+ Err(LayoutError::NormalizationFailure(_, _)) => {
+ writeln!(
+ w,
+ "<p><strong>Note:</strong> Encountered an error during type layout; \
+ the type failed to be normalized.</p>"
+ )
+ }
+ }
+
+ writeln!(w, "</div>");
+}
+
+fn pluralize(count: usize) -> &'static str {
+ if count > 1 { "s" } else { "" }
+}
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
new file mode 100644
index 000000000..d672f0bb5
--- /dev/null
+++ b/src/librustdoc/html/render/search_index.rs
@@ -0,0 +1,589 @@
+use std::collections::hash_map::Entry;
+use std::collections::BTreeMap;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::LOCAL_CRATE;
+use rustc_span::symbol::Symbol;
+use serde::ser::{Serialize, SerializeStruct, Serializer};
+
+use crate::clean;
+use crate::clean::types::{
+ FnRetTy, Function, GenericBound, Generics, ItemId, Type, WherePredicate,
+};
+use crate::formats::cache::{Cache, OrphanImplItem};
+use crate::formats::item_type::ItemType;
+use crate::html::format::join_with_double_colon;
+use crate::html::markdown::short_markdown_summary;
+use crate::html::render::{IndexItem, IndexItemFunctionType, RenderType, RenderTypeId};
+
+/// Builds the search index from the collected metadata
+pub(crate) fn build_index<'tcx>(
+ krate: &clean::Crate,
+ cache: &mut Cache,
+ tcx: TyCtxt<'tcx>,
+) -> String {
+ let mut itemid_to_pathid = FxHashMap::default();
+ let mut crate_paths = vec![];
+
+ // Attach all orphan items to the type's definition if the type
+ // has since been learned.
+ for &OrphanImplItem { parent, ref item, ref impl_generics } in &cache.orphan_impl_items {
+ if let Some(&(ref fqp, _)) = cache.paths.get(&parent) {
+ let desc = item
+ .doc_value()
+ .map_or_else(String::new, |s| short_markdown_summary(&s, &item.link_names(cache)));
+ cache.search_index.push(IndexItem {
+ ty: item.type_(),
+ name: item.name.unwrap().to_string(),
+ path: join_with_double_colon(&fqp[..fqp.len() - 1]),
+ desc,
+ parent: Some(parent),
+ parent_idx: None,
+ search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache),
+ aliases: item.attrs.get_doc_aliases(),
+ });
+ }
+ }
+
+ let crate_doc = krate
+ .module
+ .doc_value()
+ .map_or_else(String::new, |s| short_markdown_summary(&s, &krate.module.link_names(cache)));
+
+ // Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias,
+ // we need the alias element to have an array of items.
+ let mut aliases: BTreeMap<String, Vec<usize>> = BTreeMap::new();
+
+ // Sort search index items. This improves the compressibility of the search index.
+ cache.search_index.sort_unstable_by(|k1, k2| {
+ // `sort_unstable_by_key` produces lifetime errors
+ let k1 = (&k1.path, &k1.name, &k1.ty, &k1.parent);
+ let k2 = (&k2.path, &k2.name, &k2.ty, &k2.parent);
+ std::cmp::Ord::cmp(&k1, &k2)
+ });
+
+ // Set up alias indexes.
+ for (i, item) in cache.search_index.iter().enumerate() {
+ for alias in &item.aliases[..] {
+ aliases.entry(alias.as_str().to_lowercase()).or_default().push(i);
+ }
+ }
+
+ // Reduce `DefId` in paths into smaller sequential numbers,
+ // and prune the paths that do not appear in the index.
+ let mut lastpath = "";
+ let mut lastpathid = 0usize;
+
+ // First, on function signatures
+ let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new());
+ for item in search_index.iter_mut() {
+ fn convert_render_type(
+ ty: &mut RenderType,
+ cache: &mut Cache,
+ itemid_to_pathid: &mut FxHashMap<ItemId, usize>,
+ lastpathid: &mut usize,
+ crate_paths: &mut Vec<(ItemType, Symbol)>,
+ ) {
+ if let Some(generics) = &mut ty.generics {
+ for item in generics {
+ convert_render_type(item, cache, itemid_to_pathid, lastpathid, crate_paths);
+ }
+ }
+ let Cache { ref paths, ref external_paths, .. } = *cache;
+ let Some(id) = ty.id.clone() else {
+ assert!(ty.generics.is_some());
+ return;
+ };
+ let (itemid, path, item_type) = match id {
+ RenderTypeId::DefId(defid) => {
+ if let Some(&(ref fqp, item_type)) =
+ paths.get(&defid).or_else(|| external_paths.get(&defid))
+ {
+ (ItemId::DefId(defid), *fqp.last().unwrap(), item_type)
+ } else {
+ ty.id = None;
+ return;
+ }
+ }
+ RenderTypeId::Primitive(primitive) => (
+ ItemId::Primitive(primitive, LOCAL_CRATE),
+ primitive.as_sym(),
+ ItemType::Primitive,
+ ),
+ RenderTypeId::Index(_) => return,
+ };
+ match itemid_to_pathid.entry(itemid) {
+ Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())),
+ Entry::Vacant(entry) => {
+ let pathid = *lastpathid;
+ entry.insert(pathid);
+ *lastpathid += 1;
+ crate_paths.push((item_type, path));
+ ty.id = Some(RenderTypeId::Index(pathid));
+ }
+ }
+ }
+ if let Some(search_type) = &mut item.search_type {
+ for item in &mut search_type.inputs {
+ convert_render_type(
+ item,
+ cache,
+ &mut itemid_to_pathid,
+ &mut lastpathid,
+ &mut crate_paths,
+ );
+ }
+ for item in &mut search_type.output {
+ convert_render_type(
+ item,
+ cache,
+ &mut itemid_to_pathid,
+ &mut lastpathid,
+ &mut crate_paths,
+ );
+ }
+ }
+ }
+
+ let Cache { ref paths, .. } = *cache;
+
+ // Then, on parent modules
+ let crate_items: Vec<&IndexItem> = search_index
+ .iter_mut()
+ .map(|item| {
+ item.parent_idx =
+ item.parent.and_then(|defid| match itemid_to_pathid.entry(ItemId::DefId(defid)) {
+ Entry::Occupied(entry) => Some(*entry.get()),
+ Entry::Vacant(entry) => {
+ let pathid = lastpathid;
+ entry.insert(pathid);
+ lastpathid += 1;
+
+ if let Some(&(ref fqp, short)) = paths.get(&defid) {
+ crate_paths.push((short, *fqp.last().unwrap()));
+ Some(pathid)
+ } else {
+ None
+ }
+ }
+ });
+
+ // Omit the parent path if it is same to that of the prior item.
+ if lastpath == &item.path {
+ item.path.clear();
+ } else {
+ lastpath = &item.path;
+ }
+
+ &*item
+ })
+ .collect();
+
+ struct CrateData<'a> {
+ doc: String,
+ items: Vec<&'a IndexItem>,
+ paths: Vec<(ItemType, Symbol)>,
+ // The String is alias name and the vec is the list of the elements with this alias.
+ //
+ // To be noted: the `usize` elements are indexes to `items`.
+ aliases: &'a BTreeMap<String, Vec<usize>>,
+ }
+
+ impl<'a> Serialize for CrateData<'a> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let has_aliases = !self.aliases.is_empty();
+ let mut crate_data =
+ serializer.serialize_struct("CrateData", if has_aliases { 9 } else { 8 })?;
+ crate_data.serialize_field("doc", &self.doc)?;
+ crate_data.serialize_field(
+ "t",
+ &self.items.iter().map(|item| &item.ty).collect::<Vec<_>>(),
+ )?;
+ crate_data.serialize_field(
+ "n",
+ &self.items.iter().map(|item| &item.name).collect::<Vec<_>>(),
+ )?;
+ crate_data.serialize_field(
+ "q",
+ &self.items.iter().map(|item| &item.path).collect::<Vec<_>>(),
+ )?;
+ crate_data.serialize_field(
+ "d",
+ &self.items.iter().map(|item| &item.desc).collect::<Vec<_>>(),
+ )?;
+ crate_data.serialize_field(
+ "i",
+ &self
+ .items
+ .iter()
+ .map(|item| {
+ assert_eq!(
+ item.parent.is_some(),
+ item.parent_idx.is_some(),
+ "`{}` is missing idx",
+ item.name
+ );
+ // 0 is a sentinel, everything else is one-indexed
+ item.parent_idx.map(|x| x + 1).unwrap_or(0)
+ })
+ .collect::<Vec<_>>(),
+ )?;
+ crate_data.serialize_field(
+ "f",
+ &self
+ .items
+ .iter()
+ .map(|item| {
+ // Fake option to get `0` out as a sentinel instead of `null`.
+ // We want to use `0` because it's three less bytes.
+ enum FunctionOption<'a> {
+ Function(&'a IndexItemFunctionType),
+ None,
+ }
+ impl<'a> Serialize for FunctionOption<'a> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ match self {
+ FunctionOption::None => 0.serialize(serializer),
+ FunctionOption::Function(ty) => ty.serialize(serializer),
+ }
+ }
+ }
+ match &item.search_type {
+ Some(ty) => FunctionOption::Function(ty),
+ None => FunctionOption::None,
+ }
+ })
+ .collect::<Vec<_>>(),
+ )?;
+ crate_data.serialize_field(
+ "p",
+ &self.paths.iter().map(|(it, s)| (it, s.to_string())).collect::<Vec<_>>(),
+ )?;
+ if has_aliases {
+ crate_data.serialize_field("a", &self.aliases)?;
+ }
+ crate_data.end()
+ }
+ }
+
+ // Collect the index into a string
+ format!(
+ r#""{}":{}"#,
+ krate.name(tcx),
+ serde_json::to_string(&CrateData {
+ doc: crate_doc,
+ items: crate_items,
+ paths: crate_paths,
+ aliases: &aliases,
+ })
+ .expect("failed serde conversion")
+ // All these `replace` calls are because we have to go through JS string for JSON content.
+ .replace('\\', r"\\")
+ .replace('\'', r"\'")
+ // We need to escape double quotes for the JSON.
+ .replace("\\\"", "\\\\\"")
+ )
+}
+
+pub(crate) fn get_function_type_for_search<'tcx>(
+ item: &clean::Item,
+ tcx: TyCtxt<'tcx>,
+ impl_generics: Option<&(clean::Type, clean::Generics)>,
+ cache: &Cache,
+) -> Option<IndexItemFunctionType> {
+ let (mut inputs, mut output) = match *item.kind {
+ clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache),
+ clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
+ clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
+ _ => return None,
+ };
+
+ inputs.retain(|a| a.id.is_some() || a.generics.is_some());
+ output.retain(|a| a.id.is_some() || a.generics.is_some());
+
+ Some(IndexItemFunctionType { inputs, output })
+}
+
+fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType {
+ RenderType {
+ id: get_index_type_id(clean_type),
+ generics: if generics.is_empty() { None } else { Some(generics) },
+ }
+}
+
+fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
+ match *clean_type {
+ clean::Type::Path { ref path, .. } => Some(RenderTypeId::DefId(path.def_id())),
+ clean::DynTrait(ref bounds, _) => {
+ let path = &bounds[0].trait_;
+ Some(RenderTypeId::DefId(path.def_id()))
+ }
+ clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
+ clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
+ get_index_type_id(type_)
+ }
+ clean::BareFunction(_)
+ | clean::Generic(_)
+ | clean::ImplTrait(_)
+ | clean::Tuple(_)
+ | clean::Slice(_)
+ | clean::Array(_, _)
+ | clean::QPath { .. }
+ | clean::Infer => None,
+ }
+}
+
+/// The point of this function is to replace bounds with types.
+///
+/// i.e. `[T, U]` when you have the following bounds: `T: Display, U: Option<T>` will return
+/// `[Display, Option]`. If a type parameter has no trait bound, it is discarded.
+///
+/// Important note: It goes through generics recursively. So if you have
+/// `T: Option<Result<(), ()>>`, it'll go into `Option` and then into `Result`.
+#[instrument(level = "trace", skip(tcx, res, cache))]
+fn add_generics_and_bounds_as_types<'tcx, 'a>(
+ self_: Option<&'a Type>,
+ generics: &Generics,
+ arg: &'a Type,
+ tcx: TyCtxt<'tcx>,
+ recurse: usize,
+ res: &mut Vec<RenderType>,
+ cache: &Cache,
+) {
+ fn insert_ty(res: &mut Vec<RenderType>, ty: Type, mut generics: Vec<RenderType>) {
+ // generics and impl trait are both identified by their generics,
+ // rather than a type name itself
+ let anonymous = ty.is_full_generic() || ty.is_impl_trait();
+ let generics_empty = generics.is_empty();
+
+ if anonymous {
+ if generics_empty {
+ // This is a type parameter with no trait bounds (for example: `T` in
+ // `fn f<T>(p: T)`, so not useful for the rustdoc search because we would end up
+ // with an empty type with an empty name. Let's just discard it.
+ return;
+ } else if generics.len() == 1 {
+ // In this case, no need to go through an intermediate state if the type parameter
+ // contains only one trait bound.
+ //
+ // For example:
+ //
+ // `fn foo<T: Display>(r: Option<T>) {}`
+ //
+ // In this case, it would contain:
+ //
+ // ```
+ // [{
+ // name: "option",
+ // generics: [{
+ // name: "",
+ // generics: [
+ // name: "Display",
+ // generics: []
+ // }]
+ // }]
+ // }]
+ // ```
+ //
+ // After removing the intermediate (unnecessary) type parameter, it'll become:
+ //
+ // ```
+ // [{
+ // name: "option",
+ // generics: [{
+ // name: "Display",
+ // generics: []
+ // }]
+ // }]
+ // ```
+ //
+ // To be noted that it can work if there is ONLY ONE trait bound, otherwise we still
+ // need to keep it as is!
+ res.push(generics.pop().unwrap());
+ return;
+ }
+ }
+ let index_ty = get_index_type(&ty, generics);
+ if index_ty.id.is_none() && generics_empty {
+ return;
+ }
+ res.push(index_ty);
+ }
+
+ if recurse >= 10 {
+ // FIXME: remove this whole recurse thing when the recursion bug is fixed
+ // See #59502 for the original issue.
+ return;
+ }
+
+ // First, check if it's "Self".
+ let arg = if let Some(self_) = self_ {
+ match &*arg {
+ Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_,
+ type_ if type_.is_self_type() => self_,
+ arg => arg,
+ }
+ } else {
+ arg
+ };
+
+ // If this argument is a type parameter and not a trait bound or a type, we need to look
+ // for its bounds.
+ if let Type::Generic(arg_s) = *arg {
+ // First we check if the bounds are in a `where` predicate...
+ if let Some(where_pred) = generics.where_predicates.iter().find(|g| match g {
+ WherePredicate::BoundPredicate { ty, .. } => ty.def_id(cache) == arg.def_id(cache),
+ _ => false,
+ }) {
+ let mut ty_generics = Vec::new();
+ let bounds = where_pred.get_bounds().unwrap_or_else(|| &[]);
+ for bound in bounds.iter() {
+ if let GenericBound::TraitBound(poly_trait, _) = bound {
+ for param_def in poly_trait.generic_params.iter() {
+ match &param_def.kind {
+ clean::GenericParamDefKind::Type { default: Some(ty), .. } => {
+ add_generics_and_bounds_as_types(
+ self_,
+ generics,
+ ty,
+ tcx,
+ recurse + 1,
+ &mut ty_generics,
+ cache,
+ )
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+ insert_ty(res, arg.clone(), ty_generics);
+ }
+ // Otherwise we check if the trait bounds are "inlined" like `T: Option<u32>`...
+ if let Some(bound) = generics.params.iter().find(|g| g.is_type() && g.name == arg_s) {
+ let mut ty_generics = Vec::new();
+ for bound in bound.get_bounds().unwrap_or(&[]) {
+ if let Some(path) = bound.get_trait_path() {
+ let ty = Type::Path { path };
+ add_generics_and_bounds_as_types(
+ self_,
+ generics,
+ &ty,
+ tcx,
+ recurse + 1,
+ &mut ty_generics,
+ cache,
+ );
+ }
+ }
+ insert_ty(res, arg.clone(), ty_generics);
+ }
+ } else if let Type::ImplTrait(ref bounds) = *arg {
+ let mut ty_generics = Vec::new();
+ for bound in bounds {
+ if let Some(path) = bound.get_trait_path() {
+ let ty = Type::Path { path };
+ add_generics_and_bounds_as_types(
+ self_,
+ generics,
+ &ty,
+ tcx,
+ recurse + 1,
+ &mut ty_generics,
+ cache,
+ );
+ }
+ }
+ insert_ty(res, arg.clone(), ty_generics);
+ } else {
+ // This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're
+ // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't.
+ //
+ // So in here, we can add it directly and look for its own type parameters (so for `Option`,
+ // we will look for them but not for `T`).
+ let mut ty_generics = Vec::new();
+ if let Some(arg_generics) = arg.generics() {
+ for gen in arg_generics.iter() {
+ add_generics_and_bounds_as_types(
+ self_,
+ generics,
+ gen,
+ tcx,
+ recurse + 1,
+ &mut ty_generics,
+ cache,
+ );
+ }
+ }
+ insert_ty(res, arg.clone(), ty_generics);
+ }
+}
+
+/// Return the full list of types when bounds have been resolved.
+///
+/// i.e. `fn foo<A: Display, B: Option<A>>(x: u32, y: B)` will return
+/// `[u32, Display, Option]`.
+fn get_fn_inputs_and_outputs<'tcx>(
+ func: &Function,
+ tcx: TyCtxt<'tcx>,
+ impl_generics: Option<&(clean::Type, clean::Generics)>,
+ cache: &Cache,
+) -> (Vec<RenderType>, Vec<RenderType>) {
+ let decl = &func.decl;
+
+ let combined_generics;
+ let (self_, generics) = if let Some(&(ref impl_self, ref impl_generics)) = impl_generics {
+ match (impl_generics.is_empty(), func.generics.is_empty()) {
+ (true, _) => (Some(impl_self), &func.generics),
+ (_, true) => (Some(impl_self), impl_generics),
+ (false, false) => {
+ let mut params = func.generics.params.clone();
+ params.extend(impl_generics.params.clone());
+ let mut where_predicates = func.generics.where_predicates.clone();
+ where_predicates.extend(impl_generics.where_predicates.clone());
+ combined_generics = clean::Generics { params, where_predicates };
+ (Some(impl_self), &combined_generics)
+ }
+ }
+ } else {
+ (None, &func.generics)
+ };
+
+ let mut all_types = Vec::new();
+ for arg in decl.inputs.values.iter() {
+ let mut args = Vec::new();
+ add_generics_and_bounds_as_types(self_, generics, &arg.type_, tcx, 0, &mut args, cache);
+ if !args.is_empty() {
+ all_types.extend(args);
+ } else {
+ all_types.push(get_index_type(&arg.type_, vec![]));
+ }
+ }
+
+ let mut ret_types = Vec::new();
+ match decl.output {
+ FnRetTy::Return(ref return_type) => {
+ add_generics_and_bounds_as_types(
+ self_,
+ generics,
+ return_type,
+ tcx,
+ 0,
+ &mut ret_types,
+ cache,
+ );
+ if ret_types.is_empty() {
+ ret_types.push(get_index_type(return_type, vec![]));
+ }
+ }
+ _ => {}
+ };
+ (all_types, ret_types)
+}
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
new file mode 100644
index 000000000..34d590fb2
--- /dev/null
+++ b/src/librustdoc/html/render/span_map.rs
@@ -0,0 +1,203 @@
+use crate::clean::{self, PrimitiveType};
+use crate::html::sources;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{ExprKind, HirId, Mod, Node};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::hygiene::MacroKind;
+use rustc_span::{BytePos, ExpnKind, Span};
+
+use std::path::{Path, PathBuf};
+
+/// This enum allows us to store two different kinds of information:
+///
+/// In case the `span` definition comes from the same crate, we can simply get the `span` and use
+/// it as is.
+///
+/// Otherwise, we store the definition `DefId` and will generate a link to the documentation page
+/// instead of the source code directly.
+#[derive(Debug)]
+pub(crate) enum LinkFromSrc {
+ Local(clean::Span),
+ External(DefId),
+ Primitive(PrimitiveType),
+}
+
+/// This function will do at most two things:
+///
+/// 1. Generate a `span` correspondance map which links an item `span` to its definition `span`.
+/// 2. Collect the source code files.
+///
+/// It returns the `krate`, the source code files and the `span` correspondance map.
+///
+/// Note about the `span` correspondance map: the keys are actually `(lo, hi)` of `span`s. We don't
+/// need the `span` context later on, only their position, so instead of keep a whole `Span`, we
+/// only keep the `lo` and `hi`.
+pub(crate) fn collect_spans_and_sources(
+ tcx: TyCtxt<'_>,
+ krate: &clean::Crate,
+ src_root: &Path,
+ include_sources: bool,
+ generate_link_to_definition: bool,
+) -> (FxHashMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
+ let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
+
+ if include_sources {
+ if generate_link_to_definition {
+ tcx.hir().walk_toplevel_module(&mut visitor);
+ }
+ let sources = sources::collect_local_sources(tcx, src_root, krate);
+ (sources, visitor.matches)
+ } else {
+ (Default::default(), Default::default())
+ }
+}
+
+struct SpanMapVisitor<'tcx> {
+ pub(crate) tcx: TyCtxt<'tcx>,
+ pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
+}
+
+impl<'tcx> SpanMapVisitor<'tcx> {
+ /// This function is where we handle `hir::Path` elements and add them into the "span map".
+ fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
+ let info = match path.res {
+ // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
+ // Would be nice to support them too alongside the other `DefKind`
+ // (such as primitive types!).
+ Res::Def(kind, def_id) if kind != DefKind::TyParam => Some(def_id),
+ Res::Local(_) => None,
+ Res::PrimTy(p) => {
+ // FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
+ self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
+ return;
+ }
+ Res::Err => return,
+ _ => return,
+ };
+ if let Some(span) = self.tcx.hir().res_span(path.res) {
+ self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
+ } else if let Some(def_id) = info {
+ self.matches.insert(path.span, LinkFromSrc::External(def_id));
+ }
+ }
+
+ /// Adds the macro call into the span map. Returns `true` if the `span` was inside a macro
+ /// expansion, whether or not it was added to the span map.
+ ///
+ /// The idea for the macro support is to check if the current `Span` comes from expansion. If
+ /// so, we loop until we find the macro definition by using `outer_expn_data` in a loop.
+ /// Finally, we get the information about the macro itself (`span` if "local", `DefId`
+ /// otherwise) and store it inside the span map.
+ fn handle_macro(&mut self, span: Span) -> bool {
+ if !span.from_expansion() {
+ return false;
+ }
+ // So if the `span` comes from a macro expansion, we need to get the original
+ // macro's `DefId`.
+ let mut data = span.ctxt().outer_expn_data();
+ let mut call_site = data.call_site;
+ // Macros can expand to code containing macros, which will in turn be expanded, etc.
+ // So the idea here is to "go up" until we're back to code that was generated from
+ // macro expansion so that we can get the `DefId` of the original macro that was at the
+ // origin of this expansion.
+ while call_site.from_expansion() {
+ data = call_site.ctxt().outer_expn_data();
+ call_site = data.call_site;
+ }
+
+ let macro_name = match data.kind {
+ ExpnKind::Macro(MacroKind::Bang, macro_name) => macro_name,
+ // Even though we don't handle this kind of macro, this `data` still comes from
+ // expansion so we return `true` so we don't go any deeper in this code.
+ _ => return true,
+ };
+ let link_from_src = match data.macro_def_id {
+ Some(macro_def_id) if macro_def_id.is_local() => {
+ LinkFromSrc::Local(clean::Span::new(data.def_site))
+ }
+ Some(macro_def_id) => LinkFromSrc::External(macro_def_id),
+ None => return true,
+ };
+ let new_span = data.call_site;
+ let macro_name = macro_name.as_str();
+ // The "call_site" includes the whole macro with its "arguments". We only want
+ // the macro name.
+ let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
+ self.matches.insert(new_span, link_from_src);
+ true
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
+ if self.handle_macro(path.span) {
+ return;
+ }
+ self.handle_path(path);
+ intravisit::walk_path(self, path);
+ }
+
+ fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
+ // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another
+ // file, we want to link to it. Otherwise no need to create a link.
+ if !span.overlaps(m.spans.inner_span) {
+ // Now that we confirmed it's a file import, we want to get the span for the module
+ // name only and not all the "mod foo;".
+ if let Some(Node::Item(item)) = self.tcx.hir().find(id) {
+ self.matches.insert(
+ item.ident.span,
+ LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
+ );
+ }
+ }
+ intravisit::walk_mod(self, m, id);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
+ if let ExprKind::MethodCall(segment, ..) = expr.kind {
+ if let Some(hir_id) = segment.hir_id {
+ let hir = self.tcx.hir();
+ let body_id = hir.enclosing_body_owner(hir_id);
+ // FIXME: this is showing error messages for parts of the code that are not
+ // compiled (because of cfg)!
+ //
+ // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352
+ let typeck_results = self.tcx.typeck_body(
+ hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"),
+ );
+ if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
+ self.matches.insert(
+ segment.ident.span,
+ match hir.span_if_local(def_id) {
+ Some(span) => LinkFromSrc::Local(clean::Span::new(span)),
+ None => LinkFromSrc::External(def_id),
+ },
+ );
+ }
+ }
+ } else if self.handle_macro(expr.span) {
+ // We don't want to go deeper into the macro.
+ return;
+ }
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
+ if self.handle_macro(path.span) {
+ return;
+ }
+ self.handle_path(path);
+ intravisit::walk_use(self, path, id);
+ }
+}
diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs
new file mode 100644
index 000000000..3175fbe56
--- /dev/null
+++ b/src/librustdoc/html/render/tests.rs
@@ -0,0 +1,54 @@
+use std::cmp::Ordering;
+
+use super::print_item::compare_names;
+use super::{AllTypes, Buffer};
+
+#[test]
+fn test_compare_names() {
+ for &(a, b) in &[
+ ("hello", "world"),
+ ("", "world"),
+ ("123", "hello"),
+ ("123", ""),
+ ("123test", "123"),
+ ("hello", ""),
+ ("hello", "hello"),
+ ("hello123", "hello123"),
+ ("hello123", "hello12"),
+ ("hello12", "hello123"),
+ ("hello01abc", "hello01xyz"),
+ ("hello0abc", "hello0"),
+ ("hello0", "hello0abc"),
+ ("01", "1"),
+ ] {
+ assert_eq!(compare_names(a, b), a.cmp(b), "{:?} - {:?}", a, b);
+ }
+ assert_eq!(compare_names("u8", "u16"), Ordering::Less);
+ assert_eq!(compare_names("u32", "u16"), Ordering::Greater);
+ assert_eq!(compare_names("u8_to_f64", "u16_to_f64"), Ordering::Less);
+ assert_eq!(compare_names("u32_to_f64", "u16_to_f64"), Ordering::Greater);
+ assert_eq!(compare_names("u16_to_f64", "u16_to_f64"), Ordering::Equal);
+ assert_eq!(compare_names("u16_to_f32", "u16_to_f64"), Ordering::Less);
+}
+
+#[test]
+fn test_name_sorting() {
+ let names = [
+ "Apple", "Banana", "Fruit", "Fruit0", "Fruit00", "Fruit01", "Fruit1", "Fruit02", "Fruit2",
+ "Fruit20", "Fruit30x", "Fruit100", "Pear",
+ ];
+ let mut sorted = names.to_owned();
+ sorted.sort_by(|&l, r| compare_names(l, r));
+ assert_eq!(names, sorted);
+}
+
+#[test]
+fn test_all_types_prints_header_once() {
+ // Regression test for #82477
+ let all_types = AllTypes::new();
+
+ let mut buffer = Buffer::new();
+ all_types.print(&mut buffer);
+
+ assert_eq!(1, buffer.into_inner().matches("List of all items").count());
+}
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
new file mode 100644
index 000000000..6fb41ff32
--- /dev/null
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -0,0 +1,600 @@
+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 std::rc::Rc;
+use std::sync::LazyLock as Lazy;
+
+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::{EmitType, RenderOptions};
+use crate::docfs::PathError;
+use crate::error::Error;
+use crate::html::{layout, static_files};
+use crate::{try_err, try_none};
+
+static FILES_UNVERSIONED: Lazy<FxHashMap<&str, &[u8]>> = Lazy::new(|| {
+ map! {
+ "FiraSans-Regular.woff2" => static_files::fira_sans::REGULAR,
+ "FiraSans-Medium.woff2" => static_files::fira_sans::MEDIUM,
+ "FiraSans-LICENSE.txt" => static_files::fira_sans::LICENSE,
+ "SourceSerif4-Regular.ttf.woff2" => static_files::source_serif_4::REGULAR,
+ "SourceSerif4-Bold.ttf.woff2" => static_files::source_serif_4::BOLD,
+ "SourceSerif4-It.ttf.woff2" => static_files::source_serif_4::ITALIC,
+ "SourceSerif4-LICENSE.md" => static_files::source_serif_4::LICENSE,
+ "SourceCodePro-Regular.ttf.woff2" => static_files::source_code_pro::REGULAR,
+ "SourceCodePro-Semibold.ttf.woff2" => static_files::source_code_pro::SEMIBOLD,
+ "SourceCodePro-It.ttf.woff2" => static_files::source_code_pro::ITALIC,
+ "SourceCodePro-LICENSE.txt" => static_files::source_code_pro::LICENSE,
+ "NanumBarunGothic.ttf.woff2" => static_files::nanum_barun_gothic::REGULAR,
+ "NanumBarunGothic-LICENSE.txt" => static_files::nanum_barun_gothic::LICENSE,
+ "LICENSE-MIT.txt" => static_files::LICENSE_MIT,
+ "LICENSE-APACHE.txt" => static_files::LICENSE_APACHE,
+ "COPYRIGHT.txt" => static_files::COPYRIGHT,
+ }
+});
+
+enum SharedResource<'a> {
+ /// This file will never change, no matter what toolchain is used to build it.
+ ///
+ /// It does not have a resource suffix.
+ Unversioned { name: &'static str },
+ /// This file may change depending on the toolchain.
+ ///
+ /// It has a resource suffix.
+ ToolchainSpecific { basename: &'static str },
+ /// This file may change for any crate within a build, or based on the CLI arguments.
+ ///
+ /// This differs from normal invocation-specific files because it has a resource suffix.
+ InvocationSpecific { basename: &'a str },
+}
+
+impl SharedResource<'_> {
+ fn extension(&self) -> Option<&OsStr> {
+ use SharedResource::*;
+ match self {
+ Unversioned { name }
+ | ToolchainSpecific { basename: name }
+ | InvocationSpecific { basename: name } => Path::new(name).extension(),
+ }
+ }
+
+ fn path(&self, cx: &Context<'_>) -> PathBuf {
+ match self {
+ SharedResource::Unversioned { name } => cx.dst.join(name),
+ SharedResource::ToolchainSpecific { basename } => cx.suffix_path(basename),
+ SharedResource::InvocationSpecific { basename } => cx.suffix_path(basename),
+ }
+ }
+
+ fn should_emit(&self, emit: &[EmitType]) -> bool {
+ if emit.is_empty() {
+ return true;
+ }
+ let kind = match self {
+ SharedResource::Unversioned { .. } => EmitType::Unversioned,
+ SharedResource::ToolchainSpecific { .. } => EmitType::Toolchain,
+ SharedResource::InvocationSpecific { .. } => EmitType::InvocationSpecific,
+ };
+ emit.contains(&kind)
+ }
+}
+
+impl Context<'_> {
+ fn suffix_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 write_shared(
+ &self,
+ resource: SharedResource<'_>,
+ contents: impl 'static + Send + AsRef<[u8]>,
+ emit: &[EmitType],
+ ) -> Result<(), Error> {
+ if resource.should_emit(emit) {
+ self.shared.fs.write(resource.path(self), contents)
+ } else {
+ Ok(())
+ }
+ }
+
+ fn write_minify(
+ &self,
+ resource: SharedResource<'_>,
+ contents: impl 'static + Send + AsRef<str> + AsRef<[u8]>,
+ minify: bool,
+ emit: &[EmitType],
+ ) -> Result<(), Error> {
+ if minify {
+ let contents = contents.as_ref();
+ let contents = if resource.extension() == Some(OsStr::new("css")) {
+ minifier::css::minify(contents)
+ .map_err(|e| {
+ Error::new(format!("failed to minify CSS file: {}", e), resource.path(self))
+ })?
+ .to_string()
+ } else {
+ minifier::js::minify(contents).to_string()
+ };
+ self.write_shared(resource, contents, emit)
+ } else {
+ self.write_shared(resource, contents, emit)
+ }
+ }
+}
+
+pub(super) fn write_shared(
+ cx: &mut 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);
+
+ // Minified resources are usually toolchain resources. If they're not, they should use `cx.write_minify` directly.
+ fn write_minify(
+ basename: &'static str,
+ contents: impl 'static + Send + AsRef<str> + AsRef<[u8]>,
+ cx: &Context<'_>,
+ options: &RenderOptions,
+ ) -> Result<(), Error> {
+ cx.write_minify(
+ SharedResource::ToolchainSpecific { basename },
+ contents,
+ options.enable_minification,
+ &options.emit,
+ )
+ }
+
+ // Toolchain resources should never be dynamic.
+ let write_toolchain = |p: &'static _, c: &'static _| {
+ cx.write_shared(SharedResource::ToolchainSpecific { basename: p }, c, &options.emit)
+ };
+
+ // Crate resources should always be dynamic.
+ let write_crate = |p: &_, make_content: &dyn Fn() -> Result<Vec<u8>, Error>| {
+ let content = make_content()?;
+ cx.write_shared(SharedResource::InvocationSpecific { basename: p }, content, &options.emit)
+ };
+
+ // Given "foo.svg", return e.g. "url(\"foo1.58.0.svg\")"
+ fn ver_url(cx: &Context<'_>, basename: &'static str) -> String {
+ format!(
+ "url(\"{}\")",
+ SharedResource::ToolchainSpecific { basename }
+ .path(cx)
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ )
+ }
+
+ // We use the AUTOREPLACE mechanism to inject into our static JS and CSS certain
+ // values that are only known at doc build time. Since this mechanism is somewhat
+ // surprising when reading the code, please limit it to rustdoc.css.
+ write_minify(
+ "rustdoc.css",
+ static_files::RUSTDOC_CSS
+ .replace(
+ "/* AUTOREPLACE: */url(\"toggle-minus.svg\")",
+ &ver_url(cx, "toggle-minus.svg"),
+ )
+ .replace("/* AUTOREPLACE: */url(\"toggle-plus.svg\")", &ver_url(cx, "toggle-plus.svg"))
+ .replace("/* AUTOREPLACE: */url(\"down-arrow.svg\")", &ver_url(cx, "down-arrow.svg")),
+ cx,
+ options,
+ )?;
+
+ // 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("settings.css", static_files::SETTINGS_CSS, cx, options)?;
+ write_minify("noscript.css", static_files::NOSCRIPT_CSS, cx, options)?;
+
+ // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
+ // then we'll run over the "official" styles.
+ let mut themes: FxHashSet<String> = FxHashSet::default();
+
+ for entry in &cx.shared.style_files {
+ let theme = entry.basename()?;
+ let extension =
+ try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
+
+ // Handle the official themes
+ match theme.as_str() {
+ "light" => write_minify("light.css", static_files::themes::LIGHT, cx, options)?,
+ "dark" => write_minify("dark.css", static_files::themes::DARK, cx, options)?,
+ "ayu" => write_minify("ayu.css", static_files::themes::AYU, cx, options)?,
+ _ => {
+ // Handle added third-party themes
+ let filename = format!("{}.{}", theme, extension);
+ write_crate(&filename, &|| Ok(try_err!(fs::read(&entry.path), &entry.path)))?;
+ }
+ };
+
+ themes.insert(theme.to_owned());
+ }
+
+ if (*cx.shared).layout.logo.is_empty() {
+ write_toolchain("rust-logo.svg", static_files::RUST_LOGO_SVG)?;
+ }
+ if (*cx.shared).layout.favicon.is_empty() {
+ write_toolchain("favicon.svg", static_files::RUST_FAVICON_SVG)?;
+ write_toolchain("favicon-16x16.png", static_files::RUST_FAVICON_PNG_16)?;
+ write_toolchain("favicon-32x32.png", static_files::RUST_FAVICON_PNG_32)?;
+ }
+ write_toolchain("wheel.svg", static_files::WHEEL_SVG)?;
+ write_toolchain("clipboard.svg", static_files::CLIPBOARD_SVG)?;
+ write_toolchain("down-arrow.svg", static_files::DOWN_ARROW_SVG)?;
+ write_toolchain("toggle-minus.svg", static_files::TOGGLE_MINUS_PNG)?;
+ write_toolchain("toggle-plus.svg", static_files::TOGGLE_PLUS_PNG)?;
+
+ let mut themes: Vec<&String> = themes.iter().collect();
+ themes.sort();
+
+ write_minify("main.js", static_files::MAIN_JS, cx, options)?;
+ write_minify("search.js", static_files::SEARCH_JS, cx, options)?;
+ write_minify("settings.js", static_files::SETTINGS_JS, cx, options)?;
+
+ if cx.include_sources {
+ write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT, cx, options)?;
+ }
+
+ write_minify("storage.js", static_files::STORAGE_JS, cx, options)?;
+
+ if cx.shared.layout.scrape_examples_extension {
+ cx.write_minify(
+ SharedResource::InvocationSpecific { basename: "scrape-examples.js" },
+ static_files::SCRAPE_EXAMPLES_JS,
+ options.enable_minification,
+ &options.emit,
+ )?;
+ }
+
+ if let Some(ref css) = cx.shared.layout.css_file_extension {
+ let buffer = try_err!(fs::read_to_string(css), css);
+ // This varies based on the invocation, so it can't go through the write_minify wrapper.
+ cx.write_minify(
+ SharedResource::InvocationSpecific { basename: "theme.css" },
+ buffer,
+ options.enable_minification,
+ &options.emit,
+ )?;
+ }
+ write_minify("normalize.css", static_files::NORMALIZE_CSS, cx, options)?;
+ for (name, contents) in &*FILES_UNVERSIONED {
+ cx.write_shared(SharedResource::Unversioned { name }, contents, &options.emit)?;
+ }
+
+ fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec<String>, Vec<String>)> {
+ let mut ret = Vec::new();
+ let mut krates = Vec::new();
+
+ if path.exists() {
+ 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<String>, Vec<String>)> {
+ 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<OsString, Hierarchy>,
+ elems: FxHashSet<OsString>,
+ }
+
+ impl Hierarchy {
+ fn new(elem: OsString) -> Hierarchy {
+ Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
+ }
+
+ fn to_json_string(&self) -> String {
+ let mut subs: Vec<&Hierarchy> = self.children.values().collect();
+ subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
+ let mut files = self
+ .elems
+ .iter()
+ .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
+ .collect::<Vec<_>>();
+ files.sort_unstable();
+ let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(",");
+ let dirs = if subs.is_empty() && files.is_empty() {
+ String::new()
+ } else {
+ format!(",[{}]", subs)
+ };
+ let files = files.join(",");
+ let files = if files.is_empty() { String::new() } else { format!(",[{}]", files) };
+ format!(
+ "[\"{name}\"{dirs}{files}]",
+ name = self.elem.to_str().expect("invalid osstring conversion"),
+ dirs = dirs,
+ files = files
+ )
+ }
+ }
+
+ if cx.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 make_sources = || {
+ let (mut all_sources, _krates) =
+ try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst);
+ all_sources.push(format!(
+ r#""{}":{}"#,
+ &krate.name(cx.tcx()),
+ hierarchy
+ .to_json_string()
+ // All these `replace` calls are because we have to go through JS string for JSON content.
+ .replace('\\', r"\\")
+ .replace('\'', r"\'")
+ // We need to escape double quotes for the JSON.
+ .replace("\\\"", "\\\\\"")
+ ));
+ all_sources.sort();
+ let mut v = String::from("var sourcesIndex = JSON.parse('{\\\n");
+ v.push_str(&all_sources.join(",\\\n"));
+ v.push_str("\\\n}');\ncreateSourceSidebar();\n");
+ Ok(v.into_bytes())
+ };
+ write_crate("source-files.js", &make_sources)?;
+ }
+
+ // 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(cx.tcx()).as_str()), &dst);
+ all_indexes.push(search_index);
+ krates.push(krate.name(cx.tcx()).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();
+ write_crate("search-index.js", &|| {
+ let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
+ v.push_str(&all_indexes.join(",\\\n"));
+ v.push_str(
+ r#"\
+}');
+if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
+if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
+"#,
+ );
+ Ok(v.into_bytes())
+ })?;
+
+ write_crate("crates.js", &|| {
+ let krates = krates.iter().map(|k| format!("\"{}\"", k)).join(",");
+ Ok(format!("window.ALL_CRATES = [{}];", krates).into_bytes())
+ })?;
+
+ 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 shared = Rc::clone(&cx.shared);
+ let dst = cx.dst.join("index.html");
+ let page = layout::Page {
+ title: "Index of crates",
+ css_class: "mod",
+ root_path: "./",
+ static_root_path: shared.static_root_path.as_deref(),
+ description: "List of crates",
+ keywords: BASIC_KEYWORDS,
+ resource_suffix: &shared.resource_suffix,
+ };
+
+ let content = format!(
+ "<h1 class=\"fqn\">\
+ <span class=\"in-band\">List of all crates</span>\
+ </h1><ul class=\"crate mod\">{}</ul>",
+ krates
+ .iter()
+ .map(|s| {
+ format!(
+ "<li><a class=\"crate mod\" href=\"{}index.html\">{}</a></li>",
+ ensure_trailing_slash(s),
+ s
+ )
+ })
+ .collect::<String>()
+ );
+ let v = layout::render(&shared.layout, &page, "", content, &shared.style_files);
+ shared.fs.write(dst, v)?;
+ }
+ }
+
+ // Update the list of all implementors for traits
+ let dst = cx.dst.join("implementors");
+ let cache = cx.cache();
+ for (&did, imps) in &cache.implementors {
+ // Private modules can leak through to this phase of rustdoc, which
+ // could contain implementations for otherwise private types. In some
+ // rare cases we could find an implementation for an item which wasn't
+ // indexed, so we just skip this step in that case.
+ //
+ // FIXME: this is a vague explanation for why this can't be a `get`, in
+ // theory it should be...
+ let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
+ Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
+ Some((_, t)) => (p, t),
+ None => continue,
+ },
+ None => match cache.external_paths.get(&did) {
+ Some((p, t)) => (p, t),
+ None => continue,
+ },
+ };
+
+ #[derive(Serialize)]
+ struct Implementor {
+ text: String,
+ synthetic: bool,
+ types: Vec<String>,
+ }
+
+ let implementors = imps
+ .iter()
+ .filter_map(|imp| {
+ // If the trait and implementation are in the same crate, then
+ // there's no need to emit information about it (there's inlining
+ // going on). If they're in different crates then the crate defining
+ // the trait will be interested in our implementation.
+ //
+ // If the implementation is from another crate then that crate
+ // should add it.
+ if imp.impl_item.item_id.krate() == did.krate || !imp.impl_item.item_id.is_local() {
+ None
+ } else {
+ Some(Implementor {
+ text: imp.inner_impl().print(false, cx).to_string(),
+ synthetic: imp.inner_impl().kind.is_auto(),
+ types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache),
+ })
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Only create a js file if we have impls to add to it. If the trait is
+ // documented locally though we always create the file to avoid dead
+ // links.
+ if implementors.is_empty() && !cache.paths.contains_key(&did) {
+ continue;
+ }
+
+ let implementors = format!(
+ r#"implementors["{}"] = {};"#,
+ krate.name(cx.tcx()),
+ serde_json::to_string(&implementors).unwrap()
+ );
+
+ let mut mydst = dst.clone();
+ for part in &remote_path[..remote_path.len() - 1] {
+ mydst.push(part.to_string());
+ }
+ 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(cx.tcx()).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(())
+}