1
Fork 0

Rollup merge of #108784 - clubby789:askama-sidebar, r=jsha,GuillaumeGomez

rustdoc: Migrate sidebar rendering to Askama

cc #108757

Renders the sidebar for documentation using an Askama template
This commit is contained in:
Matthias Krüger 2023-03-11 12:55:43 +01:00 committed by GitHub
commit 25794194fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 615 additions and 643 deletions

View file

@ -30,6 +30,7 @@ mod tests;
mod context;
mod print_item;
mod sidebar;
mod span_map;
mod write_shared;
@ -50,11 +51,9 @@ use askama::Template;
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},
@ -1881,154 +1880,6 @@ pub(crate) fn render_impl_summary(
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
@ -2094,232 +1945,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 {}&lt;Target={}&gt;",
Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))),
Escape(&format!("{:#}", real_target.print(cx))),
);
// We want links' order to be reproducible so we don't use unstable sort.
ret.sort();
print_sidebar_block(out, id, &title, ret.iter());
}
}
// Recurse into any further impls that might exist for `target`
if let Some(target_did) = target.def_id(c) &&
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))),
@ -2340,131 +1965,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`.
@ -2495,89 +1995,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,
@ -2731,54 +2148,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