From 2f166d1a15cf0bec0869f2e955ac656eeaec48d7 Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sun, 5 Mar 2023 15:12:00 +0000 Subject: [PATCH] Render doc sidebar using Askama --- src/librustdoc/html/render/context.rs | 29 +- src/librustdoc/html/render/mod.rs | 633 +-------------------- src/librustdoc/html/render/sidebar.rs | 561 ++++++++++++++++++ src/librustdoc/html/templates/sidebar.html | 37 ++ 4 files changed, 616 insertions(+), 644 deletions(-) create mode 100644 src/librustdoc/html/render/sidebar.rs create mode 100644 src/librustdoc/html/templates/sidebar.html diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 1030fe7474758..595ddddc7dd64 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 @@ -600,15 +602,18 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { }; let all = shared.all.replace(AllTypes::new()); let mut sidebar = Buffer::html(); - write!(sidebar, "

Crate {}

", crate_name); - - let mut items = Buffer::html(); - sidebar_module_like(&mut items, all.item_sections()); - if !items.is_empty() { - sidebar.push_str("
"); - sidebar.push_buffer(items); - sidebar.push_str("
"); - } + + 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(), + }; + + bar.render_into(&mut sidebar).unwrap(); let v = layout::render( &shared.layout, diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e6a040d02e565..c389dbf379e45 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; @@ -49,11 +50,9 @@ use std::string::ToString; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def::CtorKind; use rustc_hir::def_id::{DefId, 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}, @@ -1869,154 +1868,6 @@ pub(crate) fn render_impl_summary( w.write_str(""); } -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, - "

{}{}

", - match *it.kind { - clean::ModuleItem(..) => - if it.is_crate() { - "Crate " - } else { - "Module " - }, - _ => "", - }, - it.name.as_ref().unwrap() - ); - } - - buffer.write_str("
"); - if it.is_crate() { - write!(buffer, ""); - } - - 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, "

In {}

", path); - } - - // Closes sidebar-elems div. - buffer.write_str("
"); -} - -fn get_next_url(used_links: &mut FxHashSet, url: String) -> String { - if used_links.insert(url.clone()) { - return url; - } - let mut add = 1; - while !used_links.insert(format!("{}-{}", url, add)) { - add += 1; - } - format!("{}-{}", url, add) -} - -struct SidebarLink { - name: Symbol, - url: String, -} - -impl fmt::Display for SidebarLink { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", 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 { - 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, - deref_mut: bool, - tcx: TyCtxt<'_>, -) -> Vec { - 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::>() -} - -fn get_associated_constants( - i: &clean::Impl, - used_links: &mut FxHashSet, -) -> Vec { - 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::>() -} - 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 +1933,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!("{}{}", encoded, prefix, out); - if links.insert(generated.clone()) { Some(generated) } else { None } - }) - .collect::>(); - 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::>(); - 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::>(); - 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::, _>(|i| i.inner_impl().kind.is_auto()); - let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = - concrete.into_iter().partition::, _>(|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, -) { - 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 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::>(); - 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, "
{}
", 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 +1953,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, "

{}

", id, title); -} - -fn print_sidebar_block( - buf: &mut Buffer, - id: &str, - title: &str, - items: impl Iterator, -) { - print_sidebar_title(buf, id, title); - buf.push_str("
    "); - for item in items { - write!(buf, "
  • {}
  • ", item); - } - buf.push_str("
"); -} - -fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) { - buf.write_str("
"); - - 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::>(); - - 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!("{0}", sym, ItemType::AssocType), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-associated-types", - "Provided Associated Types", - |m| m.is_associated_type(), - |sym| format!("{0}", sym, ItemType::AssocType), - ); - - print_sidebar_section( - buf, - &t.items, - "required-associated-consts", - "Required Associated Constants", - |m| m.is_ty_associated_const(), - |sym| format!("{0}", sym, ItemType::AssocConst), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-associated-consts", - "Provided Associated Constants", - |m| m.is_associated_const(), - |sym| format!("{0}", sym, ItemType::AssocConst), - ); - - print_sidebar_section( - buf, - &t.items, - "required-methods", - "Required Methods", - |m| m.is_ty_method(), - |sym| format!("{0}", sym, ItemType::TyMethod), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-methods", - "Provided Methods", - |m| m.is_method(), - |sym| format!("{0}", 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::>(); - - if !res.is_empty() { - res.sort(); - print_sidebar_block( - buf, - "foreign-impls", - "Implementations on Foreign Types", - res.iter().map(|(name, id)| format!("{}", 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("
") -} - /// 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 Trait for &T`. @@ -2483,89 +1983,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, "
{}
", 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, "
{}
", sidebar.into_inner()); - } -} - -fn get_struct_fields_name(fields: &[clean::Item]) -> Vec { - let mut fields = fields - .iter() - .filter(|f| matches!(*f.kind, clean::StructFieldItem(..))) - .filter_map(|f| { - f.name.map(|name| format!("{name}", name = name)) - }) - .collect::>(); - 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, "
{}
", 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!("{name}", name = name)) - }) - .collect::>(); - 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, "
{}
", sidebar.into_inner()); - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum ItemSection { Reexports, @@ -2719,54 +2136,6 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { } } -pub(crate) fn sidebar_module_like(buf: &mut Buffer, item_sections_in_use: FxHashSet) { - 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, "
  • {}
  • ", sec.id(), sec.name()); - } - - if !sidebar.is_empty() { - write!( - buf, - "
    \ -
      {}
    \ -
    ", - 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, "
    {}
    ", 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 diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs new file mode 100644 index 0000000000000..94ad4753d7cb6 --- /dev/null +++ b/src/librustdoc/html/render/sidebar.rs @@ -0,0 +1,561 @@ +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>, + pub(super) path: String, +} + +impl<'a> Sidebar<'a> { + /// Only create a `
    ` 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 `

    ` tag + heading: Link<'a>, + links: Vec>, + /// Render the heading even if there are no links + force_render: bool, +} + +impl<'a> LinkBlock<'a> { + pub fn new(heading: Link<'a>, links: Vec>) -> 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>, name: impl Into>) -> 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> = 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_ref().map(String::as_str).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> { + 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::>>(); + fields.sort(); + fields +} + +fn sidebar_struct<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + s: &'a clean::Struct, +) -> Vec> { + 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> { + fn filter_items<'a>( + items: &'a [clean::Item], + filt: impl Fn(&clean::Item) -> bool, + ty: &str, + ) -> Vec> { + 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::>>(); + 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> = [ + ("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> { + 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> { + 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> { + 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>, +) { + 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::, _>(|i| i.inner_impl().kind.is_auto()); + let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = + concrete.into_iter().partition::, _>(|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>, + impl_: &Impl, + v: &[Impl], + derefs: &mut DefIdSet, + used_links: &mut FxHashSet, +) { + 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 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::>(); + 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 {:#}", + 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> { + let mut variants = e + .variants() + .filter_map(|v| v.name) + .map(|name| Link::new(format!("variant.{name}"), name.to_string())) + .collect::>(); + 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, +) -> 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> { + 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::>>(); + 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, 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, + deref_mut: bool, + tcx: TyCtxt<'_>, +) -> Vec> { + 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::>() +} + +fn get_associated_constants<'a>( + i: &'a clean::Impl, + used_links: &mut FxHashSet, +) -> Vec> { + 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::>() +} diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html new file mode 100644 index 0000000000000..01d476ad29f2f --- /dev/null +++ b/src/librustdoc/html/templates/sidebar.html @@ -0,0 +1,37 @@ +{% if !title.is_empty() %} +

    {# #} + {{title_prefix}}{{title}} {# #} +

    +{% endif %} +