diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:20:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:20:39 +0000 |
commit | 1376c5a617be5c25655d0d7cb63e3beaa5a6e026 (patch) | |
tree | 3bb8d61aee02bc7a15eab3f36e3b921afc2075d0 /src/librustdoc/html/render | |
parent | Releasing progress-linux version 1.69.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.tar.xz rustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.zip |
Merging upstream version 1.70.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.rs | 57 | ||||
-rw-r--r-- | src/librustdoc/html/render/mod.rs | 1092 | ||||
-rw-r--r-- | src/librustdoc/html/render/print_item.rs | 806 | ||||
-rw-r--r-- | src/librustdoc/html/render/search_index.rs | 68 | ||||
-rw-r--r-- | src/librustdoc/html/render/sidebar.rs | 558 | ||||
-rw-r--r-- | src/librustdoc/html/render/span_map.rs | 6 |
6 files changed, 1356 insertions, 1231 deletions
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 5e4a59562..ac5054ce1 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -17,10 +17,11 @@ 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, sidebar_module_like, AllTypes, - LinkFromSrc, StylePath, + collect_spans_and_sources, scrape_examples_help, + sidebar::print_sidebar, + sidebar::{sidebar_module_like, Sidebar}, + AllTypes, LinkFromSrc, StylePath, }; - use crate::clean::{self, types::ExternalLocation, ExternalCrate}; use crate::config::{ModuleSorting, RenderOptions}; use crate::docfs::{DocFS, PathError}; @@ -35,6 +36,7 @@ use crate::html::url_parts_builder::UrlPartsBuilder; use crate::html::{layout, sources, static_files}; use crate::scrape_examples::AllCallLocations; use crate::try_err; +use askama::Template; /// Major driving force in all rustdoc rendering. This contains information /// about where in the tree-like hierarchy rendering is occurring and controls @@ -350,7 +352,7 @@ impl<'tcx> Context<'tcx> { }, ); - path = href.into_inner().to_string_lossy().to_string(); + path = href.into_inner().to_string_lossy().into_owned(); if let Some(c) = path.as_bytes().last() && *c != b'/' { path.push('/'); @@ -600,17 +602,18 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { }; let all = shared.all.replace(AllTypes::new()); let mut sidebar = Buffer::html(); - if shared.cache.crate_version.is_some() { - write!(sidebar, "<h2 class=\"location\">Crate {}</h2>", crate_name) + + let blocks = sidebar_module_like(all.item_sections()); + let bar = Sidebar { + title_prefix: "Crate ", + title: crate_name.as_str(), + is_crate: false, + version: "", + blocks: vec![blocks], + path: String::new(), }; - let mut items = Buffer::html(); - sidebar_module_like(&mut items, all.item_sections()); - if !items.is_empty() { - sidebar.push_str("<div class=\"sidebar-elems\">"); - sidebar.push_buffer(items); - sidebar.push_str("</div>"); - } + bar.render_into(&mut sidebar).unwrap(); let v = layout::render( &shared.layout, @@ -649,11 +652,35 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { </noscript>\ <link rel=\"stylesheet\" \ href=\"{static_root_path}{settings_css}\">\ - <script defer src=\"{static_root_path}{settings_js}\"></script>", + <script defer src=\"{static_root_path}{settings_js}\"></script>\ + <link rel=\"preload\" href=\"{static_root_path}{theme_light_css}\" \ + as=\"style\">\ + <link rel=\"preload\" href=\"{static_root_path}{theme_dark_css}\" \ + as=\"style\">\ + <link rel=\"preload\" href=\"{static_root_path}{theme_ayu_css}\" \ + as=\"style\">", static_root_path = page.get_static_root_path(), settings_css = static_files::STATIC_FILES.settings_css, settings_js = static_files::STATIC_FILES.settings_js, - ) + theme_light_css = static_files::STATIC_FILES.theme_light_css, + theme_dark_css = static_files::STATIC_FILES.theme_dark_css, + theme_ayu_css = static_files::STATIC_FILES.theme_ayu_css, + ); + // Pre-load all theme CSS files, so that switching feels seamless. + // + // When loading settings.html as a popover, the equivalent HTML is + // generated in main.js. + for file in &shared.style_files { + if let Ok(theme) = file.basename() { + write!( + buf, + "<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \ + as=\"style\">", + root_path = page.static_root_path.unwrap_or(""), + suffix = page.resource_suffix, + ); + } + } }, &shared.style_files, ); diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e6a040d02..463184aca 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -30,6 +30,7 @@ mod tests; mod context; mod print_item; +mod sidebar; mod span_map; mod write_shared; @@ -37,7 +38,6 @@ 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::{self, Write}; use std::fs; use std::iter::Peekable; @@ -46,14 +46,14 @@ use std::rc::Rc; use std::str; use std::string::ToString; +use askama::Template; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; +use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def::CtorKind; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::Mutability; use rustc_middle::middle::stability; -use rustc_middle::ty; use rustc_middle::ty::TyCtxt; use rustc_span::{ symbol::{sym, Symbol}, @@ -69,7 +69,7 @@ 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, + display_fn, href, join_with_double_colon, print_abi_with_space, print_constness_with_space, print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space, Buffer, Ending, HrefError, PrintWithSpace, }; @@ -104,6 +104,7 @@ pub(crate) struct IndexItem { pub(crate) parent_idx: Option<usize>, pub(crate) search_type: Option<IndexItemFunctionType>, pub(crate) aliases: Box<[Symbol]>, + pub(crate) deprecation: Option<Deprecation>, } /// A type used for the search index. @@ -407,149 +408,153 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String { ) } -fn document( - w: &mut Buffer, - cx: &mut Context<'_>, - item: &clean::Item, - parent: Option<&clean::Item>, +fn document<'a, 'cx: 'a>( + cx: &'a mut Context<'cx>, + item: &'a clean::Item, + parent: Option<&'a clean::Item>, heading_offset: HeadingOffset, -) { +) -> impl fmt::Display + 'a + Captures<'cx> { 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); - } + + display_fn(move |f| { + document_item_info(cx, item, parent).render_into(f).unwrap(); + if parent.is_none() { + write!(f, "{}", document_full_collapsible(item, cx, heading_offset))?; + } else { + write!(f, "{}", document_full(item, cx, heading_offset))?; + } + Ok(()) + }) } /// Render md_text as markdown. -fn render_markdown( - w: &mut Buffer, - cx: &mut Context<'_>, - md_text: &str, +fn render_markdown<'a, 'cx: 'a>( + cx: &'a mut Context<'cx>, + md_text: &'a 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() - ) +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| { + write!( + f, + "<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, +fn document_short<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, + link: AssocItemLink<'a>, + parent: &'a 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, has_more_content) = - MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| { + document_item_info(cx, item, Some(parent)).render_into(f).unwrap(); + if !show_def_docs { + return Ok(()); + } + if let Some(s) = item.doc_value() { + let (mut summary_html, has_more_content) = + MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); - if has_more_content { - let link = format!(r#" <a{}>Read more</a>"#, assoc_href_attr(item, link, cx)); + if has_more_content { + 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); + 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,); - } + write!(f, "<div class='docblock'>{}</div>", summary_html)?; + } + Ok(()) + }) } -fn document_full_collapsible( - w: &mut Buffer, - item: &clean::Item, - cx: &mut Context<'_>, +fn document_full_collapsible<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, heading_offset: HeadingOffset, -) { - document_full_inner(w, item, cx, true, heading_offset); +) -> impl fmt::Display + 'a + Captures<'cx> { + document_full_inner(item, cx, true, heading_offset) } -fn document_full( - w: &mut Buffer, - item: &clean::Item, - cx: &mut Context<'_>, +fn document_full<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, heading_offset: HeadingOffset, -) { - document_full_inner(w, item, cx, false, heading_offset); +) -> impl fmt::Display + 'a + Captures<'cx> { + document_full_inner(item, cx, false, heading_offset) } -fn document_full_inner( - w: &mut Buffer, - item: &clean::Item, - cx: &mut Context<'_>, +fn document_full_inner<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, 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=\"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); +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| { + if let Some(s) = item.collapsed_doc_value() { + debug!("Doc block: =====\n{}\n=====", s); + if is_collapsible { + write!( + f, + "<details class=\"toggle top-doc\" open>\ + <summary class=\"hideme\">\ + <span>Expand description</span>\ + </summary>{}</details>", + render_markdown(cx, &s, item.links(cx), heading_offset) + )?; + } else { + write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?; + } } - } - let kind = match &*item.kind { - clean::ItemKind::StrippedItem(box kind) | kind => kind, - }; + 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); - } + if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind { + render_call_locations(f, cx, item); + } + Ok(()) + }) } +#[derive(Template)] +#[template(path = "item_info.html")] +struct ItemInfo { + items: Vec<ShortItemInfo>, +} /// 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>"); - } +) -> ItemInfo { + let items = short_item_info(item, cx, parent); + ItemInfo { items } } fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> { @@ -567,7 +572,25 @@ fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<Strin cfg ); - Some(format!("<div class=\"stab portability\">{}</div>", cfg?.render_long_html())) + Some(cfg?.render_long_html()) +} + +#[derive(Template)] +#[template(path = "short_item_info.html")] +enum ShortItemInfo { + /// A message describing the deprecation of this item + Deprecation { + message: String, + }, + /// The feature corresponding to an unstable item, and optionally + /// a tracking issue URL and number. + Unstable { + feature: String, + tracking: Option<(String, u32)>, + }, + Portability { + message: String, + }, } /// Render the stability, deprecation and portability information that is displayed at the top of @@ -576,7 +599,7 @@ fn short_item_info( item: &clean::Item, cx: &mut Context<'_>, parent: Option<&clean::Item>, -) -> Vec<String> { +) -> Vec<ShortItemInfo> { let mut extra_info = vec![]; if let Some(depr @ Deprecation { note, since, is_since_rustc_version: _, suggestion: _ }) = @@ -602,15 +625,10 @@ fn short_item_info( if let Some(note) = note { let note = note.as_str(); let html = MarkdownItemInfo(note, &mut cx.id_map); - message.push_str(&format!(": {}", html.into_string())); - } - extra_info.push(format!( - "<div class=\"stab deprecated\">\ - <span class=\"emoji\">đź‘Ž</span>\ - <span>{}</span>\ - </div>", - message, - )); + message.push_str(": "); + message.push_str(&html.into_string()); + } + extra_info.push(ShortItemInfo::Deprecation { message }); } // Render unstable items. But don't render "rustc_private" crates (internal compiler crates). @@ -621,26 +639,17 @@ fn short_item_info( .filter(|stab| stab.feature != sym::rustc_private) .map(|stab| (stab.level, stab.feature)) { - let mut message = "<span class=\"emoji\">🔬</span>\ - <span>This is a nightly-only experimental API." - .to_owned(); - - let mut feature = format!("<code>{}</code>", Escape(feature.as_str())); - if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) { - feature.push_str(&format!( - " <a href=\"{url}{issue}\">#{issue}</a>", - url = url, - issue = issue - )); - } - - message.push_str(&format!(" ({})</span>", feature)); - - extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message)); + let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) + { + Some((url.clone(), issue.get())) + } else { + None + }; + extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking }); } - if let Some(portability) = portability(item, parent) { - extra_info.push(portability); + if let Some(message) = portability(item, parent) { + extra_info.push(ShortItemInfo::Portability { message }); } extra_info @@ -650,7 +659,7 @@ fn short_item_info( // "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages). pub(crate) fn render_impls( cx: &mut Context<'_>, - w: &mut Buffer, + mut w: impl Write, impls: &[&Impl], containing_item: &clean::Item, toggle_open_by_default: bool, @@ -662,7 +671,7 @@ pub(crate) fn render_impls( 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() }; + let mut buffer = Buffer::new(); render_impl( &mut buffer, cx, @@ -683,7 +692,7 @@ pub(crate) fn render_impls( }) .collect::<Vec<_>>(); rendered_impls.sort(); - w.write_str(&rendered_impls.join("")); + w.write_str(&rendered_impls.join("")).unwrap(); } /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item. @@ -839,7 +848,7 @@ fn assoc_method( let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; let indent_str = " "; - render_attributes_in_pre(w, meth, indent_str); + write!(w, "{}", render_attributes_in_pre(meth, indent_str)); (4, indent_str, Ending::NoNewline) } else { render_attributes_in_code(w, meth); @@ -1035,10 +1044,16 @@ fn attributes(it: &clean::Item) -> Vec<String> { // 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); - } +fn render_attributes_in_pre<'a>( + it: &'a clean::Item, + prefix: &'a str, +) -> impl fmt::Display + Captures<'a> { + crate::html::format::display_fn(move |f| { + for a in attributes(it) { + writeln!(f, "{}{}", prefix, a)?; + } + Ok(()) + }) } // When an attribute is rendered inside a <code> tag, it is formatted using @@ -1064,61 +1079,68 @@ impl<'a> AssocItemLink<'a> { } } -fn write_impl_section_heading(w: &mut Buffer, title: &str, id: &str) { +fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) { write!( w, "<h2 id=\"{id}\" class=\"small-section-header\">\ {title}\ <a href=\"#{id}\" class=\"anchor\">§</a>\ </h2>" - ); + ) + .unwrap(); } pub(crate) fn render_all_impls( - w: &mut Buffer, + mut w: impl Write, cx: &mut Context<'_>, containing_item: &clean::Item, concrete: &[&Impl], synthetic: &[&Impl], blanket_impl: &[&Impl], ) { - let mut impls = Buffer::empty_from(w); + let mut impls = Buffer::html(); render_impls(cx, &mut impls, concrete, containing_item, true); let impls = impls.into_inner(); if !impls.is_empty() { - write_impl_section_heading(w, "Trait Implementations", "trait-implementations"); - write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls); + write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations"); + write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls).unwrap(); } if !synthetic.is_empty() { - write_impl_section_heading(w, "Auto Trait Implementations", "synthetic-implementations"); - w.write_str("<div id=\"synthetic-implementations-list\">"); - render_impls(cx, w, synthetic, containing_item, false); - w.write_str("</div>"); + write_impl_section_heading( + &mut w, + "Auto Trait Implementations", + "synthetic-implementations", + ); + w.write_str("<div id=\"synthetic-implementations-list\">").unwrap(); + render_impls(cx, &mut w, synthetic, containing_item, false); + w.write_str("</div>").unwrap(); } if !blanket_impl.is_empty() { - write_impl_section_heading(w, "Blanket Implementations", "blanket-implementations"); - w.write_str("<div id=\"blanket-implementations-list\">"); - render_impls(cx, w, blanket_impl, containing_item, false); - w.write_str("</div>"); + write_impl_section_heading(&mut w, "Blanket Implementations", "blanket-implementations"); + w.write_str("<div id=\"blanket-implementations-list\">").unwrap(); + render_impls(cx, &mut w, blanket_impl, containing_item, false); + w.write_str("</div>").unwrap(); } } -fn render_assoc_items( - w: &mut Buffer, - cx: &mut Context<'_>, - containing_item: &clean::Item, +fn render_assoc_items<'a, 'cx: 'a>( + cx: &'a mut Context<'cx>, + containing_item: &'a clean::Item, it: DefId, - what: AssocItemRender<'_>, -) { + what: AssocItemRender<'a>, +) -> impl fmt::Display + 'a + Captures<'cx> { let mut derefs = DefIdSet::default(); derefs.insert(it); - render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs) + display_fn(move |f| { + render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs); + Ok(()) + }) } fn render_assoc_items_inner( - w: &mut Buffer, + mut w: &mut dyn fmt::Write, cx: &mut Context<'_>, containing_item: &clean::Item, it: DefId, @@ -1131,7 +1153,7 @@ fn render_assoc_items_inner( 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 mut tmp_buf = Buffer::html(); let (render_mode, id) = match what { AssocItemRender::All => { write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations"); @@ -1155,7 +1177,7 @@ fn render_assoc_items_inner( (RenderMode::ForDeref { mut_: deref_mut_ }, cx.derive_id(id)) } }; - let mut impls_buf = Buffer::empty_from(w); + let mut impls_buf = Buffer::html(); for i in &non_trait { render_impl( &mut impls_buf, @@ -1175,10 +1197,10 @@ fn render_assoc_items_inner( ); } if !impls_buf.is_empty() { - w.push_buffer(tmp_buf); - write!(w, "<div id=\"{}\">", id); - w.push_buffer(impls_buf); - w.write_str("</div>"); + write!(w, "{}", tmp_buf.into_inner()).unwrap(); + write!(w, "<div id=\"{}\">", id).unwrap(); + write!(w, "{}", impls_buf.into_inner()).unwrap(); + w.write_str("</div>").unwrap(); } } @@ -1188,7 +1210,7 @@ fn render_assoc_items_inner( 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); + render_deref_methods(&mut 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 @@ -1207,7 +1229,7 @@ fn render_assoc_items_inner( } fn render_deref_methods( - w: &mut Buffer, + mut w: impl Write, cx: &mut Context<'_>, impl_: &Impl, container_item: &clean::Item, @@ -1239,10 +1261,10 @@ fn render_deref_methods( return; } } - render_assoc_items_inner(w, cx, container_item, did, what, derefs); + render_assoc_items_inner(&mut 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); + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs); } } } @@ -1291,7 +1313,7 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O 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()) { + if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) { // Two different types might have the same did, // without actually being the same. continue; @@ -1327,7 +1349,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { for i in impls { let impl_ = i.inner_impl(); - if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) { + if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) { // Two different types might have the same did, // without actually being the same. continue; @@ -1472,37 +1494,45 @@ fn render_impl( // 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); + document_item_info(cx, it, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); + write!( + &mut doc_buffer, + "{}", + document_full(item, cx, HeadingOffset::H5) + ); short_documented = false; } else { // In case the item isn't documented, // provide short documentation from the trait. - document_short( + write!( &mut doc_buffer, - it, - cx, - link, - parent, - rendering_params.show_def_docs, + "{}", + document_short( + it, + cx, + link, + parent, + rendering_params.show_def_docs, + ) ); } } } else { - document_item_info(&mut info_buffer, cx, item, Some(parent)); + document_item_info(cx, item, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); if rendering_params.show_def_docs { - document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); + write!(&mut doc_buffer, "{}", document_full(item, cx, HeadingOffset::H5)); short_documented = false; } } } else { - document_short( + write!( &mut doc_buffer, - item, - cx, - link, - parent, - rendering_params.show_def_docs, + "{}", + document_short(item, cx, link, parent, rendering_params.show_def_docs,) ); } } @@ -1862,161 +1892,17 @@ pub(crate) fn render_impl_summary( let is_trait = 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); + write!( + w, + "<span class=\"item-info\"><div class=\"stab portability\">{}</div></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, "<ul class=\"block\">"); - 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>"); - } - - 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><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<_>>() -} - pub(crate) fn small_url_encode(s: String) -> String { // These characters don't need to be escaped in a URI. // See https://url.spec.whatwg.org/#query-percent-encode-set @@ -2082,232 +1968,6 @@ pub(crate) fn small_url_encode(s: String) -> String { } } -pub(crate) fn sidebar_render_assoc_items( - cx: &Context<'_>, - out: &mut Buffer, - id_map: &mut IdMap, - concrete: Vec<&Impl>, - synthetic: Vec<&Impl>, - blanket_impl: Vec<&Impl>, -) { - 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 concrete_format = format_impls(concrete, id_map); - let synthetic_format = format_impls(synthetic, id_map); - let blanket_format = format_impls(blanket_impl, 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_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 = DefIdSet::default(); - derefs.insert(did); - sidebar_deref_methods(cx, out, impl_, v, &mut derefs, &mut used_links); - } - - 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()); - - sidebar_render_assoc_items(cx, out, &mut id_map, concrete, synthetic, blanket_impl); - } - } -} - -fn sidebar_deref_methods( - cx: &Context<'_>, - out: &mut Buffer, - impl_: &Impl, - v: &[Impl], - derefs: &mut DefIdSet, - used_links: &mut FxHashSet<String>, -) { - 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) && - let Some(type_did) = impl_.inner_impl().for_.def_id(c) && - // `impl Deref<Target = S> for S` - (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 ret = impls - .iter() - .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(|i| get_methods(i.inner_impl(), true, 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 {}<Target={}>", - 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) && - let Some(target_impls) = c.impls.get(&target_did) && - 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, - used_links, - ); - } - } -} - -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.ctor_kind { - None => { - print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter()); - } - Some(CtorKind::Fn) => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"), - Some(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))), @@ -2328,131 +1988,6 @@ fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String } } -fn print_sidebar_title(buf: &mut Buffer, id: &str, title: &str) { - write!(buf, "<h3><a href=\"#{}\">{}</a></h3>", id, title); -} - -fn print_sidebar_block( - buf: &mut Buffer, - id: &str, - title: &str, - items: impl Iterator<Item = impl fmt::Display>, -) { - print_sidebar_title(buf, id, title); - buf.push_str("<ul class=\"block\">"); - for item in items { - write!(buf, "<li>{}</li>", item); - } - buf.push_str("</ul>"); -} - -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>") -} - /// Returns the list of implementations for the primitive reference type, filtering out any /// implementations that are on concrete or partially generic types, only keeping implementations /// of the form `impl<T> Trait for &T`. @@ -2483,89 +2018,6 @@ pub(crate) fn get_filtered_impls_for_reference<'a>( (concrete, synthetic, blanket_impl) } -fn sidebar_primitive(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { - let mut sidebar = Buffer::new(); - - if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - sidebar_assoc_items(cx, &mut sidebar, it); - } else { - let shared = Rc::clone(&cx.shared); - let (concrete, synthetic, blanket_impl) = get_filtered_impls_for_reference(&shared, it); - - sidebar_render_assoc_items( - cx, - &mut sidebar, - &mut IdMap::new(), - concrete, - synthetic, - blanket_impl, - ); - } - - 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)] pub(crate) enum ItemSection { Reexports, @@ -2719,54 +2171,6 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { } } -pub(crate) fn sidebar_module_like(buf: &mut Buffer, item_sections_in_use: FxHashSet<ItemSection>) { - use std::fmt::Write as _; - - let mut sidebar = String::new(); - - 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>\ - <ul class=\"block\">{}</ul>\ - </section>", - sidebar - ); - } -} - -fn sidebar_module(buf: &mut Buffer, items: &[clean::Item]) { - 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(); - - sidebar_module_like(buf, item_sections_in_use); -} - -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()); - } -} - /// 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 @@ -2825,7 +2229,7 @@ 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) { +fn render_call_locations<W: fmt::Write>(mut w: W, 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); @@ -2834,7 +2238,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // 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, + &mut w, "<div class=\"docblock scraped-example-list\">\ <span></span>\ <h5 id=\"{id}\">\ @@ -2843,7 +2247,8 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite </h5>", root_path = cx.root_path(), id = id - ); + ) + .unwrap(); // 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) { @@ -2861,7 +2266,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite }; // 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 write_example = |mut w: &mut W, (path, call_data): (&PathBuf, &CallData)| -> bool { let contents = match fs::read_to_string(&path) { Ok(contents) => contents, Err(err) => { @@ -2909,7 +2314,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite let locations_encoded = serde_json::to_string(&line_ranges).unwrap(); write!( - w, + &mut w, "<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\ <div class=\"scraped-example-title\">\ {name} (<a href=\"{url}\">{title}</a>)\ @@ -2922,10 +2327,12 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // The locations are encoded as a data attribute, so they can be read // later by the JS for interactions. locations = Escape(&locations_encoded) - ); + ) + .unwrap(); if line_ranges.len() > 1 { - write!(w, r#"<button class="prev">≺</button> <button class="next">≻</button>"#); + write!(w, r#"<button class="prev">≺</button> <button class="next">≻</button>"#) + .unwrap(); } // Look for the example file in the source map if it exists, otherwise return a dummy span @@ -2952,7 +2359,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite decoration_info.insert("highlight", byte_ranges); sources::print_src( - w, + &mut w, contents_subset, file_span, cx, @@ -2960,7 +2367,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite highlight::DecorationInfo(decoration_info), sources::SourceContext::Embedded { offset: line_min, needs_expansion }, ); - write!(w, "</div></div>"); + write!(w, "</div></div>").unwrap(); true }; @@ -2994,7 +2401,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // 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<_>| { + let write_and_skip_failure = |w: &mut W, it: &mut Peekable<_>| { while let Some(example) = it.next() { if write_example(&mut *w, example) { break; @@ -3003,7 +2410,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite }; // Write just one example that's visible by default in the method's description. - write_and_skip_failure(w, &mut it); + write_and_skip_failure(&mut w, &mut it); // Then add the remaining examples in a hidden section. if it.peek().is_some() { @@ -3016,17 +2423,19 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite <div class=\"hide-more\">Hide additional examples</div>\ <div class=\"more-scraped-examples\">\ <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>" - ); + ) + .unwrap(); // 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); + write_and_skip_failure(&mut 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>"#); + write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#) + .unwrap(); it.for_each(|(_, call_data)| { let (url, _) = link_to_loc(call_data, &call_data.locations[0]); write!( @@ -3034,13 +2443,14 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite r#"<li><a href="{url}">{name}</a></li>"#, url = url, name = call_data.display_name - ); + ) + .unwrap(); }); - write!(w, "</ul></div>"); + write!(w, "</ul></div>").unwrap(); } - write!(w, "</div></details>"); + write!(w, "</div></details>").unwrap(); } - write!(w, "</div>"); + write!(w, "</div>").unwrap(); } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 2869a3961..9a968e48b 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,5 +1,6 @@ use clean::AttributesExt; +use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; @@ -28,8 +29,8 @@ 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, - visibility_print_with_space, Buffer, Ending, PrintWithSpace, + display_fn, join_with_double_colon, print_abi_with_space, print_constness_with_space, + print_where_clause, visibility_print_with_space, Buffer, Ending, PrintWithSpace, }; use crate::html::layout::Page; use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine}; @@ -201,7 +202,7 @@ fn should_hide_fields(n_fields: usize) -> bool { n_fields > 12 } -fn toggle_open(w: &mut Buffer, text: impl fmt::Display) { +fn toggle_open(mut w: impl fmt::Write, text: impl fmt::Display) { write!( w, "<details class=\"toggle type-contents-toggle\">\ @@ -209,15 +210,16 @@ fn toggle_open(w: &mut Buffer, text: impl fmt::Display) { <span>Show {}</span>\ </summary>", text - ); + ) + .unwrap(); } -fn toggle_close(w: &mut Buffer) { - w.write_str("</details>"); +fn toggle_close(mut w: impl fmt::Write) { + w.write_str("</details>").unwrap(); } fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) { - document(w, cx, item, None, HeadingOffset::H2); + write!(w, "{}", document(cx, item, None, HeadingOffset::H2)); let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>(); @@ -367,7 +369,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: ..myitem.clone() }; - let stab_tags = Some(extra_info_tags(&import_item, item, cx.tcx())); + let stab_tags = Some(extra_info_tags(&import_item, item, cx.tcx()).to_string()); stab_tags } else { None @@ -461,41 +463,62 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: /// 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"; +fn extra_info_tags<'a, 'tcx: 'a>( + item: &'a clean::Item, + parent: &'a clean::Item, + tcx: TyCtxt<'tcx>, +) -> impl fmt::Display + 'a + Captures<'tcx> { + display_fn(move |f| { + fn tag_html<'a>( + class: &'a str, + title: &'a str, + contents: &'a str, + ) -> impl fmt::Display + 'a { + display_fn(move |f| { + write!( + f, + r#"<span class="stab {}" title="{}">{}</span>"#, + class, + Escape(title), + contents + ) + }) } - 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"); - } + // 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 message = if stability::deprecation_in_effect(depr) { + "Deprecated" + } else { + "Deprecation planned" + }; + write!(f, "{}", tag_html("deprecated", "", message))?; + } - let cfg = match (&item.cfg, parent.cfg.as_ref()) { - (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), - (cfg, _) => cfg.as_deref().cloned(), - }; + // 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) + { + write!(f, "{}", tag_html("unstable", "", "Experimental"))?; + } - debug!("Portability name={:?} {:?} - {:?} = {:?}", item.name, item.cfg, parent.cfg, cfg); - if let Some(ref cfg) = cfg { - tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html()); - } + let cfg = match (&item.cfg, parent.cfg.as_ref()) { + (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), + (cfg, _) => cfg.as_deref().cloned(), + }; - tags + debug!("Portability name={:?} {:?} - {:?} = {:?}", item.name, item.cfg, parent.cfg, cfg); + if let Some(ref cfg) = cfg { + write!( + f, + "{}", + tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html()) + ) + } else { + Ok(()) + } + }) } fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &clean::Function) { @@ -522,12 +545,12 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx)); wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); w.reserve(header_len); write!( w, - "{vis}{constness}{asyncness}{unsafety}{abi}fn \ + "{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}", + attrs = render_attributes_in_pre(it, ""), vis = visibility, constness = constness, asyncness = asyncness, @@ -540,7 +563,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle notable_traits = notable_traits.unwrap_or_default(), ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); } fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) { @@ -558,17 +581,17 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: let must_implement_one_of_functions = tcx.trait_def(t.def_id).must_implement_one_of.clone(); // Output the trait definition - wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); + wrap_item(w, |mut w| { write!( w, - "{}{}{}trait {}{}{}", + "{attrs}{}{}{}trait {}{}{}", visibility_print_with_space(it.visibility(tcx), it.item_id, cx), t.unsafety(tcx).print_with_space(), if t.is_auto(tcx) { "auto " } else { "" }, it.name.unwrap(), t.generics.print(cx), - bounds + bounds, + attrs = render_attributes_in_pre(it, ""), ); if !t.generics.where_predicates.is_empty() { @@ -588,7 +611,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: if should_hide_fields(count_types) { toggle = true; toggle_open( - w, + &mut w, format_args!("{} associated items", count_types + count_consts + count_methods), ); } @@ -612,7 +635,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: if !toggle && should_hide_fields(count_types + count_consts) { toggle = true; toggle_open( - w, + &mut w, format_args!( "{} associated constant{} and {} method{}", count_consts, @@ -640,7 +663,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } if !toggle && should_hide_fields(count_methods) { toggle = true; - toggle_open(w, format_args!("{} methods", count_methods)); + toggle_open(&mut w, format_args!("{} methods", count_methods)); } if count_consts != 0 && count_methods != 0 { w.write_str("\n"); @@ -688,14 +711,14 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } } if toggle { - toggle_close(w); + toggle_close(&mut w); } w.write_str("}"); } }); // Trait documentation - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { write!( @@ -713,7 +736,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: 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); + write!(&mut content, "{}", document(cx, m, Some(t), HeadingOffset::H5)); let toggled = !content.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; @@ -825,7 +848,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } // 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); + write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)); let cloned_shared = Rc::clone(&cx.shared); let cache = &cloned_shared.cache; @@ -858,8 +881,8 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: 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)); + synthetic.sort_by_cached_key(|i| ImplString::new(i, cx)); + concrete.sort_by_cached_key(|i| ImplString::new(i, cx)); if !foreign.is_empty() { write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", ""); @@ -1035,147 +1058,201 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TraitAlias) { wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); write!( w, - "trait {}{}{} = {};", + "{attrs}trait {}{}{} = {};", it.name.unwrap(), t.generics.print(cx), print_where_clause(&t.generics, cx, 0, Ending::Newline), - bounds(&t.bounds, true, cx) + bounds(&t.bounds, true, cx), + attrs = render_attributes_in_pre(it, ""), ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(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) + write!(w, "{}", render_assoc_items(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_item(w, |w| { - render_attributes_in_pre(w, it, ""); write!( w, - "type {}{}{where_clause} = impl {bounds};", + "{attrs}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), + attrs = render_attributes_in_pre(it, ""), ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(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) + write!(w, "{}", render_assoc_items(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, |w| { - render_attributes_in_pre(w, it, ""); write!( w, - "{}type {}{}{where_clause} = {type_};", + "{attrs}{}type {}{}{where_clause} = {type_};", visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), it.name.unwrap(), t.generics.print(cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), type_ = t.type_.print(cx), + attrs = render_attributes_in_pre(it, ""), ); }); } write_content(w, cx, it, t); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(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); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); } fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) { - wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); - render_union(w, it, Some(&s.generics), &s.fields, cx); - }); + #[derive(Template)] + #[template(path = "item_union.html")] + struct ItemUnion<'a, 'cx> { + cx: std::cell::RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + s: &'a clean::Union, + } - document(w, cx, it, None, HeadingOffset::H2); + impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { + fn render_assoc_items<'b>( + &'b self, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let def_id = self.it.item_id.expect_def_id(); + let mut cx = self.cx.borrow_mut(); + let v = render_assoc_items(*cx, self.it, def_id, AssocItemRender::All); + write!(f, "{v}") + }) + } + fn document_type_layout<'b>( + &'b self, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let def_id = self.it.item_id.expect_def_id(); + let cx = self.cx.borrow_mut(); + let v = document_type_layout(*cx, def_id); + write!(f, "{v}") + }) + } + fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let cx = self.cx.borrow_mut(); + let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, *cx); + write!(f, "{v}") + }) + } + fn render_attributes_in_pre<'b>( + &'b self, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let v = render_attributes_in_pre(self.it, ""); + write!(f, "{v}") + }) + } + fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let mut cx = self.cx.borrow_mut(); + let v = document(*cx, self.it, None, HeadingOffset::H2); + write!(f, "{v}") + }) + } + fn document_field<'b>( + &'b self, + field: &'a clean::Item, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let mut cx = self.cx.borrow_mut(); + let v = document(*cx, field, Some(self.it), HeadingOffset::H3); + write!(f, "{v}") + }) + } + fn stability_field(&self, field: &clean::Item) -> Option<String> { + let cx = self.cx.borrow(); + field.stability_class(cx.tcx()) + } + fn print_ty<'b>( + &'b self, + ty: &'a clean::Type, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let cx = self.cx.borrow(); + let v = ty.print(*cx); + write!(f, "{v}") + }) + } - 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>", - shortty = ItemType::StructField, - ty = ty.print(cx), - ); - if let Some(stability_class) = field.stability_class(cx.tcx()) { - write!(w, "<span class=\"stab {stability_class}\"></span>"); - } - document(w, cx, field, Some(it), HeadingOffset::H3); + fn fields_iter( + &self, + ) -> std::iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>> { + self.s + .fields + .iter() + .filter_map(|f| match *f.kind { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable() } } - 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); + + ItemUnion { cx: std::cell::RefCell::new(cx), it, s }.render_into(w).unwrap(); } -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(", "); - } - match *ty.kind { - clean::StrippedItem(box clean::StructFieldItem(_)) => w.write_str("_"), - clean::StructFieldItem(ref ty) => write!(w, "{}", ty.print(cx)), - _ => unreachable!(), +fn print_tuple_struct_fields<'a, 'cx: 'a>( + cx: &'a Context<'cx>, + s: &'a [clean::Item], +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(|f| { + for (i, ty) in s.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + match *ty.kind { + clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_")?, + clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx))?, + _ => unreachable!(), + } } - } + Ok(()) + }) } fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) { let tcx = cx.tcx(); let count_variants = e.variants().count(); - wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); + wrap_item(w, |mut w| { write!( w, - "{}enum {}{}", + "{attrs}{}enum {}{}", visibility_print_with_space(it.visibility(tcx), it.item_id, cx), it.name.unwrap(), e.generics.print(cx), + attrs = render_attributes_in_pre(it, ""), ); if !print_where_clause_and_check(w, &e.generics, cx) { // If there wasn't a `where` clause, we add a whitespace. @@ -1189,7 +1266,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w.write_str("{\n"); let toggle = should_hide_fields(count_variants); if toggle { - toggle_open(w, format_args!("{} variants", count_variants)); + toggle_open(&mut w, format_args!("{} variants", count_variants)); } for v in e.variants() { w.write_str(" "); @@ -1199,9 +1276,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: clean::VariantItem(ref var) => match var.kind { clean::VariantKind::CLike => write!(w, "{}", name), clean::VariantKind::Tuple(ref s) => { - write!(w, "{}(", name); - print_tuple_struct_fields(w, cx, s); - w.write_str(")"); + write!(w, "{name}({})", print_tuple_struct_fields(cx, s),); } clean::VariantKind::Struct(ref s) => { render_struct(w, v, None, None, &s.fields, " ", false, cx); @@ -1212,28 +1287,29 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w.write_str(",\n"); } - if variants_stripped { + if variants_stripped && !it.is_non_exhaustive() { w.write_str(" // some variants omitted\n"); } if toggle { - toggle_close(w); + toggle_close(&mut w); } w.write_str("}"); } }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(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) + </h2>\ + {}\ + <div class=\"variants\">", + document_non_exhaustive_header(it), + document_non_exhaustive(it) ); - document_non_exhaustive(w, it); - write!(w, "<div class=\"variants\">"); for variant in e.variants() { let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); write!( @@ -1254,9 +1330,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() }; if let clean::VariantKind::Tuple(ref s) = variant_data.kind { - w.write_str("("); - print_tuple_struct_fields(w, cx, s); - w.write_str(")"); + write!(w, "({})", print_tuple_struct_fields(cx, s),); } w.write_str("</h3></section>"); @@ -1280,9 +1354,10 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: write!( w, "<div class=\"sub-variant\" id=\"{variant_id}\">\ - <h4>{heading}</h4>", + <h4>{heading}</h4>\ + {}", + document_non_exhaustive(variant) ); - document_non_exhaustive(w, variant); for field in fields { match *field.kind { clean::StrippedItem(box clean::StructFieldItem(_)) => {} @@ -1300,10 +1375,13 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: <code>{f}: {t}</code>\ </span>", f = field.name.unwrap(), - t = ty.print(cx) + t = ty.print(cx), + ); + write!( + w, + "{}</div>", + document(cx, field, Some(variant), HeadingOffset::H5) ); - document(w, cx, field, Some(variant), HeadingOffset::H5); - write!(w, "</div>"); } _ => unreachable!(), } @@ -1311,18 +1389,18 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w.write_str("</div>"); } - document(w, cx, variant, Some(it), HeadingOffset::H4); + write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4)); } write!(w, "</div>"); } 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); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); } fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) { highlight::render_item_decl_with_highlighting(&t.source, w); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) { @@ -1348,14 +1426,14 @@ fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &c } } }); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { let def_id = it.item_id.expect_def_id(); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - render_assoc_items(w, cx, it, def_id, AssocItemRender::All); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); } else { // We handle the "reference" primitive type on its own because we only want to list // implementations on generic types. @@ -1411,7 +1489,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle } }); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) { @@ -1420,7 +1498,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); let mut fields = s .fields @@ -1436,11 +1514,12 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean w, "<h2 id=\"fields\" class=\"fields small-section-header\">\ {}{}<a href=\"#fields\" class=\"anchor\">§</a>\ - </h2>", + </h2>\ + {}", if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, - document_non_exhaustive_header(it) + document_non_exhaustive_header(it), + document_non_exhaustive(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()); @@ -1454,13 +1533,13 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean item_type = ItemType::StructField, ty = ty.print(cx) ); - document(w, cx, field, Some(it), HeadingOffset::H3); + write!(w, "{}", document(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); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); } fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { @@ -1475,7 +1554,7 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean typ = s.type_.print(cx) ); }); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { @@ -1490,13 +1569,13 @@ fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); - render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All) + write!(w, "{}", render_assoc_items(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) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } /// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). @@ -1575,12 +1654,25 @@ where w.write_str("</code></pre>"); } -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)); +#[derive(PartialEq, Eq)] +struct ImplString(String); - // lhs and rhs are formatted as HTML, which may be unnecessary - compare_names(&lhss, &rhss) +impl ImplString { + fn new(i: &Impl, cx: &Context<'_>) -> ImplString { + ImplString(format!("{}", i.inner_impl().print(false, cx))) + } +} + +impl PartialOrd for ImplString { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(Ord::cmp(self, other)) + } +} + +impl Ord for ImplString { + fn cmp(&self, other: &Self) -> Ordering { + compare_names(&self.0, &other.0) + } } fn render_implementor( @@ -1620,64 +1712,69 @@ fn render_implementor( ); } -fn render_union( - w: &mut Buffer, - it: &clean::Item, - g: Option<&clean::Generics>, - fields: &[clean::Item], - cx: &Context<'_>, -) { - let tcx = cx.tcx(); - write!( - w, - "{}union {}", - visibility_print_with_space(it.visibility(tcx), 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); +fn render_union<'a, 'cx: 'a>( + it: &'a clean::Item, + g: Option<&'a clean::Generics>, + fields: &'a [clean::Item], + cx: &'a Context<'cx>, +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |mut f| { + let tcx = cx.tcx(); + write!( + f, + "{}union {}", + visibility_print_with_space(it.visibility(tcx), it.item_id, cx), + it.name.unwrap(), + )?; + + let where_displayed = g + .map(|g| { + let mut buf = Buffer::html(); + write!(buf, "{}", g.print(cx)); + let where_displayed = print_where_clause_and_check(&mut buf, g, cx); + write!(f, "{buf}", buf = buf.into_inner()).unwrap(); + where_displayed + }) + .unwrap_or(false); - // If there wasn't a `where` clause, we add a whitespace. - if !where_displayed { - w.write_str(" "); - } + // If there wasn't a `where` clause, we add a whitespace. + if !where_displayed { + f.write_str(" ")?; + } - write!(w, "{{\n"); - 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)); - } + write!(f, "{{\n")?; + let count_fields = + fields.iter().filter(|field| matches!(*field.kind, clean::StructFieldItem(..))).count(); + let toggle = should_hide_fields(count_fields); + if toggle { + toggle_open(&mut f, format_args!("{} fields", count_fields)); + } - for field in fields { - if let clean::StructFieldItem(ref ty) = *field.kind { - write!( - w, - " {}{}: {},\n", - visibility_print_with_space(field.visibility(tcx), field.item_id, cx), - field.name.unwrap(), - ty.print(cx) - ); + for field in fields { + if let clean::StructFieldItem(ref ty) = *field.kind { + write!( + f, + " {}{}: {},\n", + visibility_print_with_space(field.visibility(tcx), field.item_id, cx), + field.name.unwrap(), + ty.print(cx) + )?; + } } - } - if it.has_stripped_entries().unwrap() { - write!(w, " /* private fields */\n"); - } - if toggle { - toggle_close(w); - } - w.write_str("}"); + if it.has_stripped_entries().unwrap() { + write!(f, " /* private fields */\n")?; + } + if toggle { + toggle_close(&mut f); + } + f.write_str("}").unwrap(); + Ok(()) + }) } fn render_struct( - w: &mut Buffer, + mut w: &mut Buffer, it: &clean::Item, g: Option<&clean::Generics>, ty: Option<CtorKind>, @@ -1699,10 +1796,11 @@ fn render_struct( } match ty { None => { - let where_diplayed = g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false); + let where_displayed = + 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 { + if !where_displayed { w.write_str(" {"); } else { w.write_str("{"); @@ -1712,7 +1810,7 @@ fn render_struct( let has_visible_fields = count_fields > 0; let toggle = should_hide_fields(count_fields); if toggle { - toggle_open(w, format_args!("{} fields", count_fields)); + toggle_open(&mut w, format_args!("{} fields", count_fields)); } for field in fields { if let clean::StructFieldItem(ref ty) = *field.kind { @@ -1736,7 +1834,7 @@ fn render_struct( write!(w, " /* private fields */ "); } if toggle { - toggle_close(w); + toggle_close(&mut w); } w.write_str("}"); } @@ -1782,155 +1880,169 @@ 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=\"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" +fn document_non_exhaustive<'a>(item: &'a clean::Item) -> impl fmt::Display + 'a { + display_fn(|f| { + if item.is_non_exhaustive() { + write!( + f, + "<details class=\"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() { + f.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() { + f.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() { + f.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 { + f.write_str( + "This type will require a wildcard arm in any match statements or constructors.", + )?; } - ); - 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.", - ); + f.write_str("</div></details>")?; } - - w.write_str("</div></details>"); - } + Ok(()) + }) } -fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) { - fn write_size_of_layout(w: &mut Buffer, layout: &LayoutS, tag_size: u64) { +fn document_type_layout<'a, 'cx: 'a>( + cx: &'a Context<'cx>, + ty_def_id: DefId, +) -> impl fmt::Display + 'a + Captures<'cx> { + fn write_size_of_layout(mut w: impl fmt::Write, layout: &LayoutS, tag_size: u64) { if layout.abi.is_unsized() { - write!(w, "(unsized)"); + write!(w, "(unsized)").unwrap(); } else { let size = layout.size.bytes() - tag_size; - write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" },); + write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" }).unwrap(); + if layout.abi.is_uninhabited() { + write!( + w, + " (<a href=\"https://doc.rust-lang.org/stable/reference/glossary.html#uninhabited\">uninhabited</a>)" + ).unwrap(); + } } } - 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).subst_identity(); - 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, 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") - }; + display_fn(move |mut f| { + if !cx.shared.show_type_layout { + return Ok(()); + } - 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") - }; + writeln!( + f, + "<h2 id=\"layout\" class=\"small-section-header\"> \ + Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>" + )?; + writeln!(f, "<div class=\"docblock\">")?; - for (index, layout) in variants.iter_enumerated() { - let name = adt.variant(index).name; - write!(w, "<li><code>{name}</code>: "); - write_size_of_layout(w, layout, tag_size); - writeln!(w, "</li>"); + let tcx = cx.tcx(); + let param_env = tcx.param_env(ty_def_id); + let ty = tcx.type_of(ty_def_id).subst_identity(); + match tcx.layout_of(param_env.and(ty)) { + Ok(ty_layout) => { + writeln!( + f, + "<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>" + )?; + f.write_str("<p><strong>Size:</strong> ")?; + write_size_of_layout(&mut f, &ty_layout.layout.0, 0); + writeln!(f, "</p>")?; + if let Variants::Multiple { variants, tag, tag_encoding, .. } = + &ty_layout.layout.variants() + { + if !variants.is_empty() { + f.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!(&mut f, "<li><code>{name}</code>: ")?; + write_size_of_layout(&mut f, layout, tag_size); + writeln!(&mut f, "</li>")?; + } + f.write_str("</ul>")?; } - 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!( + f, + "<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!( + f, + "<p><strong>Note:</strong> Encountered an error during type layout; \ + the type was too big.</p>" + )?; + } + Err(LayoutError::NormalizationFailure(_, _)) => { + writeln!( + f, + "<p><strong>Note:</strong> Encountered an error during type layout; \ + the type failed to be normalized.</p>" + )?; + } } - // 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>"); + writeln!(f, "</div>") + }) } fn pluralize(count: usize) -> &'static str { diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 090ea2cb1..f5b4a3f5a 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -7,9 +7,7 @@ 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::clean::types::{FnRetTy, Function, Generics, ItemId, Type, WherePredicate}; use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; @@ -42,6 +40,7 @@ pub(crate) fn build_index<'tcx>( parent_idx: None, search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache), aliases: item.attrs.get_doc_aliases(), + deprecation: item.deprecation(tcx), }); } } @@ -60,7 +59,7 @@ pub(crate) fn build_index<'tcx>( // `sort_unstable_by_key` produces lifetime errors let k1 = (&k1.path, k1.name.as_str(), &k1.ty, &k1.parent); let k2 = (&k2.path, k2.name.as_str(), &k2.ty, &k2.parent); - std::cmp::Ord::cmp(&k1, &k2) + Ord::cmp(&k1, &k2) }); // Set up alias indexes. @@ -253,7 +252,17 @@ pub(crate) fn build_index<'tcx>( )?; crate_data.serialize_field( "q", - &self.items.iter().map(|item| &item.path).collect::<Vec<_>>(), + &self + .items + .iter() + .enumerate() + // Serialize as an array of item indices and full paths + .filter_map( + |(index, item)| { + if item.path.is_empty() { None } else { Some((index, &item.path)) } + }, + ) + .collect::<Vec<_>>(), )?; crate_data.serialize_field( "d", @@ -307,6 +316,16 @@ pub(crate) fn build_index<'tcx>( .collect::<Vec<_>>(), )?; crate_data.serialize_field( + "c", + &self + .items + .iter() + .enumerate() + // Serialize as an array of deprecated item indices + .filter_map(|(index, item)| item.deprecation.map(|_| index)) + .collect::<Vec<_>>(), + )?; + crate_data.serialize_field( "p", &self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::<Vec<_>>(), )?; @@ -467,7 +486,7 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>( } // First, check if it's "Self". - let arg = if let Some(self_) = self_ { + let mut arg = if let Some(self_) = self_ { match &*arg { Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_, type_ if type_.is_self_type() => self_, @@ -477,34 +496,33 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>( arg }; + // strip references from the argument type + while let Type::BorrowedRef { type_, .. } = &*arg { + arg = &*type_; + } + // 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), + for where_pred in generics.where_predicates.iter().filter(|g| match g { + WherePredicate::BoundPredicate { ty: Type::Generic(ty_s), .. } => *ty_s == arg_s, _ => 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 ¶m_def.kind { - clean::GenericParamDefKind::Type { default: Some(ty), .. } => { - add_generics_and_bounds_as_types( - self_, - generics, - ty, - tcx, - recurse + 1, - &mut ty_generics, - cache, - ) - } - _ => {} - } - } + 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); diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs new file mode 100644 index 000000000..455b4e9ae --- /dev/null +++ b/src/librustdoc/html/render/sidebar.rs @@ -0,0 +1,558 @@ +use std::{borrow::Cow, rc::Rc}; + +use askama::Template; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::{def::CtorKind, def_id::DefIdSet}; +use rustc_middle::ty::{self, TyCtxt}; + +use crate::{ + clean, + formats::{item_type::ItemType, Impl}, + html::{format::Buffer, markdown::IdMap}, +}; + +use super::{item_ty_to_section, Context, ItemSection}; + +#[derive(Template)] +#[template(path = "sidebar.html")] +pub(super) struct Sidebar<'a> { + pub(super) title_prefix: &'static str, + pub(super) title: &'a str, + pub(super) is_crate: bool, + pub(super) version: &'a str, + pub(super) blocks: Vec<LinkBlock<'a>>, + pub(super) path: String, +} + +impl<'a> Sidebar<'a> { + /// Only create a `<section>` if there are any blocks + /// which should actually be rendered. + pub fn should_render_blocks(&self) -> bool { + self.blocks.iter().any(LinkBlock::should_render) + } +} + +/// A sidebar section such as 'Methods'. +pub(crate) struct LinkBlock<'a> { + /// The name of this section, e.g. 'Methods' + /// as well as the link to it, e.g. `#implementations`. + /// Will be rendered inside an `<h3>` tag + heading: Link<'a>, + links: Vec<Link<'a>>, + /// Render the heading even if there are no links + force_render: bool, +} + +impl<'a> LinkBlock<'a> { + pub fn new(heading: Link<'a>, links: Vec<Link<'a>>) -> Self { + Self { heading, links, force_render: false } + } + + pub fn forced(heading: Link<'a>) -> Self { + Self { heading, links: vec![], force_render: true } + } + + pub fn should_render(&self) -> bool { + self.force_render || !self.links.is_empty() + } +} + +/// A link to an item. Content should not be escaped. +#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] +pub(crate) struct Link<'a> { + /// The content for the anchor tag + name: Cow<'a, str>, + /// The id of an anchor within the page (without a `#` prefix) + href: Cow<'a, str>, +} + +impl<'a> Link<'a> { + pub fn new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self { + Self { href: href.into(), name: name.into() } + } + pub fn empty() -> Link<'static> { + Link::new("", "") + } +} + +pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { + let blocks: Vec<LinkBlock<'_>> = match *it.kind { + clean::StructItem(ref s) => sidebar_struct(cx, it, s), + clean::TraitItem(ref t) => sidebar_trait(cx, it, t), + clean::PrimitiveItem(_) => sidebar_primitive(cx, it), + clean::UnionItem(ref u) => sidebar_union(cx, it, u), + clean::EnumItem(ref e) => sidebar_enum(cx, it, e), + clean::TypedefItem(_) => sidebar_typedef(cx, it), + clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)], + clean::ForeignTypeItem => sidebar_foreign_type(cx, it), + _ => vec![], + }; + // 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). + let (title_prefix, title) = if it.is_struct() + || it.is_trait() + || it.is_primitive() + || it.is_union() + || it.is_enum() + || it.is_mod() + || it.is_typedef() + { + ( + match *it.kind { + clean::ModuleItem(..) if it.is_crate() => "Crate ", + clean::ModuleItem(..) => "Module ", + _ => "", + }, + it.name.as_ref().unwrap().as_str(), + ) + } else { + ("", "") + }; + let version = + if it.is_crate() { cx.cache().crate_version.as_deref().unwrap_or_default() } else { "" }; + let path: String = if !it.is_mod() { + cx.current.iter().map(|s| s.as_str()).intersperse("::").collect() + } else { + "".into() + }; + let sidebar = Sidebar { title_prefix, title, is_crate: it.is_crate(), version, blocks, path }; + sidebar.render_into(buffer).unwrap(); +} + +fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>> { + let mut fields = fields + .iter() + .filter(|f| matches!(*f.kind, clean::StructFieldItem(..))) + .filter_map(|f| { + f.name.as_ref().map(|name| Link::new(format!("structfield.{name}"), name.as_str())) + }) + .collect::<Vec<Link<'a>>>(); + fields.sort(); + fields +} + +fn sidebar_struct<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + s: &'a clean::Struct, +) -> Vec<LinkBlock<'a>> { + let fields = get_struct_fields_name(&s.fields); + let field_name = match s.ctor_kind { + Some(CtorKind::Fn) => Some("Tuple Fields"), + None => Some("Fields"), + _ => None, + }; + let mut items = vec![]; + if let Some(name) = field_name { + items.push(LinkBlock::new(Link::new("fields", name), fields)); + } + sidebar_assoc_items(cx, it, &mut items); + items +} + +fn sidebar_trait<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + t: &'a clean::Trait, +) -> Vec<LinkBlock<'a>> { + fn filter_items<'a>( + items: &'a [clean::Item], + filt: impl Fn(&clean::Item) -> bool, + ty: &str, + ) -> Vec<Link<'a>> { + let mut res = items + .iter() + .filter_map(|m: &clean::Item| match m.name { + Some(ref name) if filt(m) => Some(Link::new(format!("{ty}.{name}"), name.as_str())), + _ => None, + }) + .collect::<Vec<Link<'a>>>(); + res.sort(); + res + } + + let req_assoc = filter_items(&t.items, |m| m.is_ty_associated_type(), "associatedtype"); + let prov_assoc = filter_items(&t.items, |m| m.is_associated_type(), "associatedtype"); + let req_assoc_const = + filter_items(&t.items, |m| m.is_ty_associated_const(), "associatedconstant"); + let prov_assoc_const = + filter_items(&t.items, |m| m.is_associated_const(), "associatedconstant"); + let req_method = filter_items(&t.items, |m| m.is_ty_method(), "tymethod"); + let prov_method = filter_items(&t.items, |m| m.is_method(), "method"); + let mut foreign_impls = vec![]; + if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) { + foreign_impls.extend( + implementors + .iter() + .filter(|i| !i.is_on_local_type(cx)) + .filter_map(|i| super::extract_for_impl_name(&i.impl_item, cx)) + .map(|(name, id)| Link::new(id, name)), + ); + foreign_impls.sort(); + } + + let mut blocks: Vec<LinkBlock<'_>> = [ + ("required-associated-types", "Required Associated Types", req_assoc), + ("provided-associated-types", "Provided Associated Types", prov_assoc), + ("required-associated-consts", "Required Associated Constants", req_assoc_const), + ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const), + ("required-methods", "Required Methods", req_method), + ("provided-methods", "Provided Methods", prov_method), + ("foreign-impls", "Implementations on Foreign Types", foreign_impls), + ] + .into_iter() + .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), items)) + .collect(); + sidebar_assoc_items(cx, it, &mut blocks); + blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"))); + if t.is_auto(cx.tcx()) { + blocks.push(LinkBlock::forced(Link::new("synthetic-implementors", "Auto Implementors"))); + } + blocks +} + +fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> { + if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items + } else { + let shared = Rc::clone(&cx.shared); + let (concrete, synthetic, blanket_impl) = + super::get_filtered_impls_for_reference(&shared, it); + + sidebar_render_assoc_items(cx, &mut IdMap::new(), concrete, synthetic, blanket_impl).into() + } +} + +fn sidebar_typedef<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +fn sidebar_union<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + u: &'a clean::Union, +) -> Vec<LinkBlock<'a>> { + let fields = get_struct_fields_name(&u.fields); + let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +/// Adds trait implementations into the blocks of links +fn sidebar_assoc_items<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + links: &mut Vec<LinkBlock<'a>>, +) { + let did = it.item_id.expect_def_id(); + let cache = cx.cache(); + + let mut assoc_consts = Vec::new(); + let mut methods = Vec::new(); + 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; + assoc_consts.extend( + v.iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)), + ); + // We want links' order to be reproducible so we don't use unstable sort. + assoc_consts.sort(); + + #[rustfmt::skip] // rustfmt makes the pipeline less readable + methods.extend( + 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())), + ); + + // We want links' order to be reproducible so we don't use unstable sort. + methods.sort(); + } + + let mut deref_methods = Vec::new(); + let [concrete, synthetic, blanket] = 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 = DefIdSet::default(); + derefs.insert(did); + sidebar_deref_methods( + cx, + &mut deref_methods, + impl_, + v, + &mut derefs, + &mut used_links, + ); + } + + 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()); + + sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl) + } else { + std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![])) + }; + + let mut blocks = vec![ + LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts), + LinkBlock::new(Link::new("implementations", "Methods"), methods), + ]; + blocks.append(&mut deref_methods); + blocks.extend([concrete, synthetic, blanket]); + links.append(&mut blocks); + } +} + +fn sidebar_deref_methods<'a>( + cx: &'a Context<'_>, + out: &mut Vec<LinkBlock<'a>>, + impl_: &Impl, + v: &[Impl], + derefs: &mut DefIdSet, + used_links: &mut FxHashSet<String>, +) { + 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) && + let Some(type_did) = impl_.inner_impl().for_.def_id(c) && + // `impl Deref<Target = S> for S` + (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 ret = impls + .iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_methods(i.inner_impl(), true, 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) { + Cow::Borrowed( + cx.deref_id_map + .get(&target_def_id) + .expect("Deref section without derived id") + .as_str(), + ) + } else { + Cow::Borrowed("deref-methods") + }; + let title = format!( + "Methods from {:#}<Target={:#}>", + impl_.inner_impl().trait_.as_ref().unwrap().print(cx), + real_target.print(cx), + ); + // We want links' order to be reproducible so we don't use unstable sort. + ret.sort(); + out.push(LinkBlock::new(Link::new(id, title), ret)); + } + } + + // Recurse into any further impls that might exist for `target` + if let Some(target_did) = target.def_id(c) && + let Some(target_impls) = c.impls.get(&target_did) && + 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, + used_links, + ); + } + } +} + +fn sidebar_enum<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + e: &'a clean::Enum, +) -> Vec<LinkBlock<'a>> { + let mut variants = e + .variants() + .filter_map(|v| v.name) + .map(|name| Link::new(format!("variant.{name}"), name.to_string())) + .collect::<Vec<_>>(); + variants.sort_unstable(); + + let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +pub(crate) fn sidebar_module_like( + item_sections_in_use: FxHashSet<ItemSection>, +) -> LinkBlock<'static> { + let item_sections = ItemSection::ALL + .iter() + .copied() + .filter(|sec| item_sections_in_use.contains(sec)) + .map(|sec| Link::new(sec.id(), sec.name())) + .collect(); + LinkBlock::new(Link::empty(), item_sections) +} + +fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> { + 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(); + + sidebar_module_like(item_sections_in_use) +} + +fn sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +/// Renders the trait implementations for this type +fn sidebar_render_assoc_items( + cx: &Context<'_>, + id_map: &mut IdMap, + concrete: Vec<&Impl>, + synthetic: Vec<&Impl>, + blanket_impl: Vec<&Impl>, +) -> [LinkBlock<'static>; 3] { + 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(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx)); + + let prefix = match it.inner_impl().polarity { + ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", + ty::ImplPolarity::Negative => "!", + }; + let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx))); + if links.insert(generated.clone()) { Some(generated) } else { None } + }) + .collect::<Vec<Link<'static>>>(); + ret.sort(); + ret + }; + + let concrete = format_impls(concrete, id_map); + let synthetic = format_impls(synthetic, id_map); + let blanket = format_impls(blanket_impl, id_map); + [ + LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete), + LinkBlock::new( + Link::new("synthetic-implementations", "Auto Trait Implementations"), + synthetic, + ), + LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket), + ] +} + +fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String { + if used_links.insert(url.clone()) { + return url; + } + let mut add = 1; + while !used_links.insert(format!("{}-{}", url, add)) { + add += 1; + } + format!("{}-{}", url, add) +} + +fn get_methods<'a>( + i: &'a clean::Impl, + for_deref: bool, + used_links: &mut FxHashSet<String>, + deref_mut: bool, + tcx: TyCtxt<'_>, +) -> Vec<Link<'a>> { + i.items + .iter() + .filter_map(|item| match item.name { + Some(ref name) if !name.is_empty() && item.is_method() => { + if !for_deref || super::should_render_item(item, deref_mut, tcx) { + Some(Link::new( + get_next_url(used_links, format!("{}.{}", ItemType::Method, name)), + name.as_str(), + )) + } else { + None + } + } + _ => None, + }) + .collect::<Vec<_>>() +} + +fn get_associated_constants<'a>( + i: &'a clean::Impl, + used_links: &mut FxHashSet<String>, +) -> Vec<Link<'a>> { + i.items + .iter() + .filter_map(|item| match item.name { + Some(ref name) if !name.is_empty() && item.is_associated_const() => Some(Link::new( + get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)), + name.as_str(), + )), + _ => None, + }) + .collect::<Vec<_>>() +} diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 4514894ca..eb9262f47 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -29,12 +29,12 @@ pub(crate) enum LinkFromSrc { /// This function will do at most two things: /// -/// 1. Generate a `span` correspondance map which links an item `span` to its definition `span`. +/// 1. Generate a `span` correspondence 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. +/// It returns the `krate`, the source code files and the `span` correspondence map. /// -/// Note about the `span` correspondance map: the keys are actually `(lo, hi)` of `span`s. We don't +/// Note about the `span` correspondence 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( |