//! Rustdoc's HTML rendering module.
//!
//! This modules contains the bulk of the logic necessary for rendering a
//! rustdoc `clean::Crate` instance to a set of static HTML pages. This
//! rendering process is largely driven by the `format!` syntax extension to
//! perform all I/O into files and streams.
//!
//! The rendering process is largely driven by the `Context` and `Cache`
//! structures. The cache is pre-populated by crawling the crate in question,
//! and then it is shared among the various rendering threads. The cache is meant
//! to be a fairly large structure not implementing `Clone` (because it's shared
//! among threads). The context, however, should be a lightweight structure. This
//! is cloned per-thread and contains information about what is currently being
//! rendered.
//!
//! In order to speed up rendering (mostly because of markdown rendering), the
//! rendering process has been parallelized. This parallelization is only
//! exposed through the `crate` method on the context, and then also from the
//! fact that the shared cache is stored in TLS (and must be accessed as such).
//!
//! In addition to rendering the crate itself, this module is also responsible
//! for creating the corresponding search index and source file renderings.
//! These threads are not parallelized (they haven't been a bottleneck yet), and
//! both occur before the crate is rendered.
crate mod cache;
#[cfg(test)]
mod tests;
mod context;
mod print_item;
mod write_shared;
crate use context::*;
use std::collections::VecDeque;
use std::default::Default;
use std::fmt;
use std::path::PathBuf;
use std::str;
use std::string::ToString;
use rustc_ast_pretty::pprust;
use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def::CtorKind;
use rustc_hir::def_id::DefId;
use rustc_hir::Mutability;
use rustc_middle::middle::stability;
use rustc_span::symbol::{kw, sym, Symbol};
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use crate::clean::{self, GetDefId, ItemId, RenderedLink, SelfTy};
use crate::docfs::PathError;
use crate::error::Error;
use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape;
use crate::html::format::{
href, print_abi_with_space, print_constness_with_space, print_default_space,
print_generic_bounds, print_where_clause, Buffer, PrintWithSpace,
};
use crate::html::markdown::{Markdown, MarkdownHtml, MarkdownSummaryLine};
/// A pair of name and its optional document.
crate type NameDoc = (String, Option(&self, serializer: S) -> Result(&self, serializer: S) -> Result{}
",
title.replace(' ', "-"), // IDs cannot contain whitespaces.
title,
class
);
for s in e.iter() {
write!(f, "
");
}
}
f.write_str(
"\
List of all items\
\
\
\
[−]\
\
",
);
// Note: print_entries does not escape the title, because we know the current set of titles
// doesn't require escaping.
print_entries(f, &self.structs, "Structs", "structs");
print_entries(f, &self.enums, "Enums", "enums");
print_entries(f, &self.unions, "Unions", "unions");
print_entries(f, &self.primitives, "Primitives", "primitives");
print_entries(f, &self.traits, "Traits", "traits");
print_entries(f, &self.macros, "Macros", "macros");
print_entries(f, &self.attributes, "Attribute Macros", "attributes");
print_entries(f, &self.derives, "Derive Macros", "derives");
print_entries(f, &self.functions, "Functions", "functions");
print_entries(f, &self.typedefs, "Typedefs", "typedefs");
print_entries(f, &self.trait_aliases, "Trait Aliases", "trait-aliases");
print_entries(f, &self.opaque_tys, "Opaque Types", "opaque-types");
print_entries(f, &self.statics, "Statics", "statics");
print_entries(f, &self.constants, "Constants", "constants")
}
}
#[derive(Debug)]
enum Setting {
Section {
description: &'static str,
sub_settings: Vec\
Rustdoc settings\
\
{}
", Escape(&feature.as_str()));
if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) {
feature.push_str(&format!(
" #{issue}",
url = url,
issue = issue
));
}
message.push_str(&format!(" ({})", feature));
if let Some(unstable_reason) = reason {
let mut ids = cx.id_map.borrow_mut();
message = format!(
"` tag, it is formatted using
// a whitespace prefix and newline.
fn render_attributes_in_pre(w: &mut Buffer, it: &clean::Item, prefix: &str) {
for a in attributes(it) {
writeln!(w, "{}{}", prefix, a);
}
}
// When an attribute is rendered inside a tag, it is formatted using
// a div to produce a newline after it.
fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item) {
for a in attributes(it) {
write!(w, "{}", a);
}
}
#[derive(Clone)]
enum AssocItemLink<'a> {
Anchor(Option<&'a str>),
GotoSource(ItemId, &'a FxHashSet),
}
impl<'a> AssocItemLink<'a> {
fn anchor(&self, id: &'a str) -> Self {
match *self {
AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(&id)),
ref other => other.clone(),
}
}
}
fn render_assoc_items(
w: &mut Buffer,
cx: &Context<'_>,
containing_item: &clean::Item,
it: DefId,
what: AssocItemRender<'_>,
) {
info!("Documenting associated items of {:?}", containing_item.name);
let v = match cx.cache.impls.get(&it) {
Some(v) => v,
None => return,
};
let cache = cx.cache();
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
if !non_trait.is_empty() {
let render_mode = match what {
AssocItemRender::All => {
w.write_str(
"\
Implementations\
",
);
RenderMode::Normal
}
AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
write!(
w,
"\
Methods from {trait_}<Target = {type_}>\
\
",
trait_ = trait_.print(cx),
type_ = type_.print(cx),
);
RenderMode::ForDeref { mut_: deref_mut_ }
}
};
for i in &non_trait {
render_impl(
w,
cx,
i,
containing_item,
AssocItemLink::Anchor(None),
render_mode,
true,
None,
false,
true,
&[],
);
}
}
if let AssocItemRender::DerefFor { .. } = what {
return;
}
if !traits.is_empty() {
let deref_impl = traits
.iter()
.find(|t| t.inner_impl().trait_.def_id_full(cache) == cx.cache.deref_trait_did);
if let Some(impl_) = deref_impl {
let has_deref_mut = traits
.iter()
.any(|t| t.inner_impl().trait_.def_id_full(cache) == cx.cache.deref_mut_trait_did);
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut);
}
let (synthetic, concrete): (Vec<&&Impl>, Vec<&&Impl>) =
traits.iter().partition(|t| t.inner_impl().synthetic);
let (blanket_impl, concrete): (Vec<&&Impl>, _) =
concrete.into_iter().partition(|t| t.inner_impl().blanket_impl.is_some());
let mut impls = Buffer::empty_from(&w);
render_impls(cx, &mut impls, &concrete, containing_item);
let impls = impls.into_inner();
if !impls.is_empty() {
write!(
w,
"\
Trait Implementations\
\
{}",
impls
);
}
if !synthetic.is_empty() {
w.write_str(
"\
Auto Trait Implementations\
\
\
",
);
render_impls(cx, w, &synthetic, containing_item);
w.write_str("");
}
if !blanket_impl.is_empty() {
w.write_str(
"\
Blanket Implementations\
\
\
",
);
render_impls(cx, w, &blanket_impl, containing_item);
w.write_str("");
}
}
}
fn render_deref_methods(
w: &mut Buffer,
cx: &Context<'_>,
impl_: &Impl,
container_item: &clean::Item,
deref_mut: bool,
) {
let deref_type = impl_.inner_impl().trait_.as_ref().unwrap();
let (target, real_target) = impl_
.inner_impl()
.items
.iter()
.find_map(|item| match *item.kind {
clean::TypedefItem(ref t, true) => Some(match *t {
clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
_ => (&t.type_, &t.type_),
}),
_ => None,
})
.expect("Expected associated type binding");
debug!("Render deref methods for {:#?}, target {:#?}", impl_.inner_impl().for_, target);
let what =
AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut };
if let Some(did) = target.def_id_full(cx.cache()) {
if let Some(type_did) = impl_.inner_impl().for_.def_id_full(cx.cache()) {
// `impl Deref for S`
if did == type_did {
// Avoid infinite cycles
return;
}
}
render_assoc_items(w, cx, container_item, did, what);
} else {
if let Some(prim) = target.primitive_type() {
if let Some(&did) = cx.cache.primitive_locations.get(&prim) {
render_assoc_items(w, cx, container_item, did, what);
}
}
}
}
fn should_render_item(item: &clean::Item, deref_mut_: bool, cache: &Cache) -> bool {
let self_type_opt = match *item.kind {
clean::MethodItem(ref method, _) => method.decl.self_type(),
clean::TyMethodItem(ref method) => method.decl.self_type(),
_ => None,
};
if let Some(self_ty) = self_type_opt {
let (by_mut_ref, by_box, by_value) = match self_ty {
SelfTy::SelfBorrowed(_, mutability)
| SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => {
(mutability == Mutability::Mut, false, false)
}
SelfTy::SelfExplicit(clean::ResolvedPath { did, .. }) => {
(false, Some(did) == cache.owned_box_did, false)
}
SelfTy::SelfValue => (false, false, true),
_ => (false, false, false),
};
(deref_mut_ || !by_mut_ref) && !by_box && !by_value
} else {
false
}
}
fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
let mut out = Buffer::html();
let mut trait_ = String::new();
if let Some(did) = decl.output.def_id_full(cx.cache()) {
if let Some(impls) = cx.cache().impls.get(&did) {
for i in impls {
let impl_ = i.inner_impl();
if impl_.trait_.def_id().map_or(false, |d| {
cx.cache().traits.get(&d).map(|t| t.is_notable).unwrap_or(false)
}) {
if out.is_empty() {
write!(
&mut out,
"Notable traits for {}
\
",
impl_.for_.print(cx)
);
trait_.push_str(&impl_.for_.print(cx).to_string());
}
//use the "where" class here to make it small
write!(
&mut out,
"{}",
impl_.print(false, cx)
);
let t_did = impl_.trait_.def_id_full(cx.cache()).unwrap();
for it in &impl_.items {
if let clean::TypedefItem(ref tydef, _) = *it.kind {
out.push_str(" ");
assoc_type(
&mut out,
it,
&[],
Some(&tydef.type_),
AssocItemLink::GotoSource(t_did.into(), &FxHashSet::default()),
"",
cx,
);
out.push_str(";");
}
}
}
}
}
}
if !out.is_empty() {
out.insert_str(
0,
"ⓘ\
",
);
out.push_str("");
}
out.into_inner()
}
fn render_impl(
w: &mut Buffer,
cx: &Context<'_>,
i: &Impl,
parent: &clean::Item,
link: AssocItemLink<'_>,
render_mode: RenderMode,
show_def_docs: bool,
use_absolute: Option,
is_on_foreign_type: bool,
show_default_items: bool,
// This argument is used to reference same type with different paths to avoid duplication
// in documentation pages for trait with automatic implementations like "Send" and "Sync".
aliases: &[String],
) {
let cache = cx.cache();
let traits = &cache.traits;
let trait_ = i.trait_did_full(cache).map(|did| &traits[&did]);
let mut close_tags = String::new();
// For trait implementations, the `interesting` output contains all methods that have doc
// comments, and the `boring` output contains all methods that do not. The distinction is
// used to allow hiding the boring methods.
// `containing_item` is used for rendering stability info. If the parent is a trait impl,
// `containing_item` will the grandparent, since trait impls can't have stability attached.
fn doc_impl_item(
boring: &mut Buffer,
interesting: &mut Buffer,
cx: &Context<'_>,
item: &clean::Item,
parent: &clean::Item,
containing_item: &clean::Item,
link: AssocItemLink<'_>,
render_mode: RenderMode,
is_default_item: bool,
trait_: Option<&clean::Trait>,
show_def_docs: bool,
) {
let item_type = item.type_();
let name = item.name.as_ref().unwrap();
let render_method_item = match render_mode {
RenderMode::Normal => true,
RenderMode::ForDeref { mut_: deref_mut_ } => {
should_render_item(&item, deref_mut_, &cx.cache)
}
};
let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" };
let mut doc_buffer = Buffer::empty_from(boring);
let mut info_buffer = Buffer::empty_from(boring);
let mut short_documented = true;
if render_method_item {
if !is_default_item {
if let Some(t) = trait_ {
// The trait item may have been stripped so we might not
// find any documentation or stability for it.
if let Some(it) = t.items.iter().find(|i| i.name == item.name) {
// We need the stability of the item from the trait
// because impls can't have a stability.
if item.doc_value().is_some() {
document_item_info(&mut info_buffer, cx, it, Some(parent));
document_full(&mut doc_buffer, item, cx);
short_documented = false;
} else {
// In case the item isn't documented,
// provide short documentation from the trait.
document_short(
&mut doc_buffer,
it,
cx,
link.clone(),
parent,
show_def_docs,
);
}
}
} else {
document_item_info(&mut info_buffer, cx, item, Some(parent));
if show_def_docs {
document_full(&mut doc_buffer, item, cx);
short_documented = false;
}
}
} else {
document_short(&mut doc_buffer, item, cx, link.clone(), parent, show_def_docs);
}
}
let w = if short_documented && trait_.is_some() { interesting } else { boring };
let toggled = !doc_buffer.is_empty();
if toggled {
let method_toggle_class =
if item_type == ItemType::Method { " method-toggle" } else { "" };
write!(w, "", method_toggle_class);
}
match *item.kind {
clean::MethodItem(..) | clean::TyMethodItem(_) => {
// Only render when the method is not static or we allow static methods
if render_method_item {
let id = cx.derive_id(format!("{}.{}", item_type, name));
let source_id = trait_
.and_then(|trait_| {
trait_.items.iter().find(|item| {
item.name.map(|n| n.as_str().eq(&name.as_str())).unwrap_or(false)
})
})
.map(|item| format!("{}.{}", item.type_(), name));
write!(
w,
"",
id, item_type, in_trait_class,
);
render_rightside(w, cx, item, containing_item);
write!(w, "", id);
w.write_str("");
render_assoc_item(
w,
item,
link.anchor(source_id.as_ref().unwrap_or(&id)),
ItemType::Impl,
cx,
);
w.write_str("
");
w.write_str("");
}
}
clean::TypedefItem(ref tydef, _) => {
let source_id = format!("{}.{}", ItemType::AssocType, name);
let id = cx.derive_id(source_id.clone());
write!(
w,
"",
id, item_type, in_trait_class
);
write!(w, "", id);
w.write_str("");
assoc_type(
w,
item,
&Vec::new(),
Some(&tydef.type_),
link.anchor(if trait_.is_some() { &source_id } else { &id }),
"",
cx,
);
w.write_str("
");
w.write_str("");
}
clean::AssocConstItem(ref ty, ref default) => {
let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
write!(
w,
"");
}
clean::AssocTypeItem(ref bounds, ref default) => {
let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
write!(w, "");
}
clean::StrippedItem(..) => return,
_ => panic!("can't make docs for trait item with name {:?}", item.name),
}
w.push_buffer(info_buffer);
if toggled {
w.write_str("
");
w.push_buffer(doc_buffer);
w.push_str("");
}
}
let mut impl_items = Buffer::empty_from(w);
let mut default_impl_items = Buffer::empty_from(w);
for trait_item in &i.inner_impl().items {
doc_impl_item(
&mut default_impl_items,
&mut impl_items,
cx,
trait_item,
if trait_.is_some() { &i.impl_item } else { parent },
parent,
link.clone(),
render_mode,
false,
trait_.map(|t| &t.trait_),
show_def_docs,
);
}
fn render_default_items(
boring: &mut Buffer,
interesting: &mut Buffer,
cx: &Context<'_>,
t: &clean::Trait,
i: &clean::Impl,
parent: &clean::Item,
containing_item: &clean::Item,
render_mode: RenderMode,
show_def_docs: bool,
) {
for trait_item in &t.items {
let n = trait_item.name;
if i.items.iter().any(|m| m.name == n) {
continue;
}
let did = i.trait_.as_ref().unwrap().def_id_full(cx.cache()).unwrap();
let provided_methods = i.provided_trait_methods(cx.tcx());
let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods);
doc_impl_item(
boring,
interesting,
cx,
trait_item,
parent,
containing_item,
assoc_link,
render_mode,
true,
Some(t),
show_def_docs,
);
}
}
// If we've implemented a trait, then also emit documentation for all
// default items which weren't overridden in the implementation block.
// We don't emit documentation for default items if they appear in the
// Implementations on Foreign Types or Implementors sections.
if show_default_items {
if let Some(t) = trait_ {
render_default_items(
&mut default_impl_items,
&mut impl_items,
cx,
&t.trait_,
&i.inner_impl(),
&i.impl_item,
parent,
render_mode,
show_def_docs,
);
}
}
if render_mode == RenderMode::Normal {
let toggled = !(impl_items.is_empty() && default_impl_items.is_empty());
if toggled {
close_tags.insert_str(0, "");
write!(w, "");
write!(w, "")
}
render_impl_summary(
w,
cx,
i,
parent,
parent,
show_def_docs,
use_absolute,
is_on_foreign_type,
aliases,
);
if toggled {
write!(w, "
")
}
if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) {
let mut ids = cx.id_map.borrow_mut();
write!(
w,
"{}",
Markdown(
&*dox,
&i.impl_item.links(cx),
&mut ids,
cx.shared.codes,
cx.shared.edition(),
&cx.shared.playground
)
.into_string()
);
}
}
if !default_impl_items.is_empty() || !impl_items.is_empty() {
w.write_str("");
w.push_buffer(default_impl_items);
w.push_buffer(impl_items);
close_tags.insert_str(0, "");
}
w.write_str(&close_tags);
}
// Render the items that appear on the right side of methods, impls, and
// associated types. For example "1.0.0 (const: 1.39.0) [src]".
fn render_rightside(
w: &mut Buffer,
cx: &Context<'_>,
item: &clean::Item,
containing_item: &clean::Item,
) {
let tcx = cx.tcx();
write!(w, "");
render_stability_since_raw(
w,
item.stable_since(tcx).as_deref(),
item.const_stability(tcx),
containing_item.stable_since(tcx).as_deref(),
containing_item.const_stable_since(tcx).as_deref(),
);
write_srclink(cx, item, w);
w.write_str("");
}
pub(crate) fn render_impl_summary(
w: &mut Buffer,
cx: &Context<'_>,
i: &Impl,
parent: &clean::Item,
containing_item: &clean::Item,
show_def_docs: bool,
use_absolute: Option,
is_on_foreign_type: bool,
// This argument is used to reference same type with different paths to avoid duplication
// in documentation pages for trait with automatic implementations like "Send" and "Sync".
aliases: &[String],
) {
let id = cx.derive_id(match i.inner_impl().trait_ {
Some(ref t) => {
if is_on_foreign_type {
get_id_for_impl_on_foreign_type(&i.inner_impl().for_, t, cx)
} else {
format!("impl-{}", small_url_encode(format!("{:#}", t.print(cx))))
}
}
None => "impl".to_string(),
});
let aliases = if aliases.is_empty() {
String::new()
} else {
format!(" data-aliases=\"{}\"", aliases.join(","))
};
write!(w, "", id, aliases);
render_rightside(w, cx, &i.impl_item, containing_item);
write!(w, "", id);
write!(w, "");
if let Some(use_absolute) = use_absolute {
write!(w, "{}", i.inner_impl().print(use_absolute, cx));
if show_def_docs {
for it in &i.inner_impl().items {
if let clean::TypedefItem(ref tydef, _) = *it.kind {
w.write_str(" ");
assoc_type(w, it, &[], Some(&tydef.type_), AssocItemLink::Anchor(None), "", cx);
w.write_str(";");
}
}
}
} else {
write!(w, "{}", i.inner_impl().print(false, cx));
}
write!(w, "
");
let is_trait = i.inner_impl().trait_.is_some();
if is_trait {
if let Some(portability) = portability(&i.impl_item, Some(parent)) {
write!(w, "{}", portability);
}
}
w.write_str("");
}
fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
let parentlen = cx.current.len() - if it.is_mod() { 1 } else { 0 };
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::StructItem(..) => "Struct ",
clean::TraitItem(..) => "Trait ",
clean::PrimitiveItem(..) => "Primitive Type ",
clean::UnionItem(..) => "Union ",
clean::EnumItem(..) => "Enum ",
clean::TypedefItem(..) => "Type Definition ",
clean::ForeignTypeItem => "Foreign Type ",
clean::ModuleItem(..) =>
if it.is_crate() {
"Crate "
} else {
"Module "
},
_ => "",
},
it.name.as_ref().unwrap()
);
}
if it.is_crate() {
if let Some(ref version) = cx.cache.crate_version {
write!(
buffer,
"\
\
Version {}
\
",
Escape(version),
);
}
}
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)
}
fn get_methods(
i: &clean::Impl,
for_deref: bool,
used_links: &mut FxHashSet,
deref_mut: bool,
cache: &Cache,
) -> Vec {
i.items
.iter()
.filter_map(|item| match item.name {
Some(ref name) if !name.is_empty() && item.is_method() => {
if !for_deref || should_render_item(item, deref_mut, cache) {
Some(format!(
"{}",
get_next_url(used_links, format!("method.{}", name)),
name
))
} else {
None
}
}
_ => None,
})
.collect::>()
}
// The point is to url encode any potential character from a type with genericity.
fn small_url_encode(s: String) -> String {
let mut st = String::new();
let mut last_match = 0;
for (idx, c) in s.char_indices() {
let escaped = match c {
'<' => "%3C",
'>' => "%3E",
' ' => "%20",
'?' => "%3F",
'\'' => "%27",
'&' => "%26",
',' => "%2C",
':' => "%3A",
';' => "%3B",
'[' => "%5B",
']' => "%5D",
'"' => "%22",
_ => continue,
};
st += &s[last_match..idx];
st += escaped;
// NOTE: we only expect single byte characters here - which is fine as long as we
// only match single byte characters
last_match = idx + 1;
}
if last_match != 0 {
st += &s[last_match..];
st
} else {
s
}
}
fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
let did = it.def_id.expect_def_id();
if let Some(v) = cx.cache.impls.get(&did) {
let mut used_links = FxHashSet::default();
let cache = cx.cache();
{
let used_links_bor = &mut used_links;
let mut ret = v
.iter()
.filter(|i| i.inner_impl().trait_.is_none())
.flat_map(move |i| {
get_methods(i.inner_impl(), false, used_links_bor, false, &cx.cache)
})
.collect::>();
if !ret.is_empty() {
// We want links' order to be reproducible so we don't use unstable sort.
ret.sort();
out.push_str(
"Methods\
>();
ret.sort();
ret
};
let write_sidebar_links = |out: &mut Buffer, links: Vec| {
out.push_str(", _>(|i| i.inner_impl().synthetic);
let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = concrete
.into_iter()
.partition::, _>(|i| i.inner_impl().blanket_impl.is_some());
let concrete_format = format_impls(concrete);
let synthetic_format = format_impls(synthetic);
let blanket_format = format_impls(blanket_impl);
if !concrete_format.is_empty() {
out.push_str(
"\
Trait Implementations",
);
write_sidebar_links(out, concrete_format);
}
if !synthetic_format.is_empty() {
out.push_str(
"\
Auto Trait Implementations",
);
write_sidebar_links(out, synthetic_format);
}
if !blanket_format.is_empty() {
out.push_str(
"\
Blanket Implementations",
);
write_sidebar_links(out, blanket_format);
}
}
}
}
fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &Vec) {
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::TypedefItem(ref t, true) => 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_full(c) {
if let Some(type_did) = impl_.inner_impl().for_.def_id_full(c) {
// `impl Deref for S`
if did == type_did {
// Avoid infinite cycles
return;
}
}
}
let deref_mut = v
.iter()
.filter(|i| i.inner_impl().trait_.is_some())
.any(|i| i.inner_impl().trait_.def_id_full(c) == c.deref_mut_trait_did);
let inner_impl = target
.def_id_full(c)
.or_else(|| {
target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned())
})
.and_then(|did| c.impls.get(&did));
if let Some(impls) = inner_impl {
debug!("found inner_impl: {:?}", impls);
let mut used_links = FxHashSet::default();
let mut ret = impls
.iter()
.filter(|i| i.inner_impl().trait_.is_none())
.flat_map(|i| get_methods(i.inner_impl(), true, &mut used_links, deref_mut, c))
.collect::>();
if !ret.is_empty() {
write!(
out,
"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();
out.push_str("{}", sidebar.into_inner());
}
}
fn get_id_for_impl_on_foreign_type(
for_: &clean::Type,
trait_: &clean::Type,
cx: &Context<'_>,
) -> String {
small_url_encode(format!("impl-{:#}-for-{:#}", trait_.print(cx), for_.print(cx),))
}
fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
match *item.kind {
clean::ItemKind::ImplItem(ref i) => {
if let Some(ref trait_) = i.trait_ {
// Alternative format produces no URLs,
// so this parameter does nothing.
Some((
format!("{:#}", i.for_.print(cx)),
get_id_for_impl_on_foreign_type(&i.for_, trait_, cx),
))
} else {
None
}
}
_ => None,
}
}
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],
before: &str,
filter: impl Fn(&clean::Item) -> bool,
write: impl Fn(&mut Buffer, &str),
after: &str,
) {
let mut items = 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();
out.push_str(before);
for item in items.into_iter() {
write(out, &item);
}
out.push_str(after);
}
}
print_sidebar_section(
buf,
&t.items,
"\
Associated Types ",
);
print_sidebar_section(
buf,
&t.items,
"\
Associated Constants ",
);
print_sidebar_section(
buf,
&t.items,
"\
Required Methods ",
);
print_sidebar_section(
buf,
&t.items,
"\
Provided Methods ",
);
if let Some(implementors) = cx.cache.implementors.get(&it.def_id.expect_def_id()) {
let cache = cx.cache();
let mut res = implementors
.iter()
.filter(|i| {
i.inner_impl()
.for_
.def_id_full(cache)
.map_or(false, |d| !cx.cache.paths.contains_key(&d))
})
.filter_map(|i| extract_for_impl_name(&i.impl_item, cx))
.collect::>();
if !res.is_empty() {
res.sort();
buf.push_str(
"\
Implementations on Foreign Types\
");
}
}
sidebar_assoc_items(cx, buf, it);
buf.push_str("Implementors");
if t.is_auto {
buf.push_str(
"Auto Implementors",
);
}
buf.push_str(" ")
}
fn sidebar_primitive(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
let mut sidebar = Buffer::new();
sidebar_assoc_items(cx, &mut sidebar, it);
if !sidebar.is_empty() {
write!(buf, "{}", 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() {
sidebar.push_str(
"Fields\
{}", 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
.iter()
.filter_map(|v| match v.name {
Some(ref name) => Some(format!("{name}", name = name)),
_ => None,
})
.collect::>();
if !variants.is_empty() {
variants.sort_unstable();
sidebar.push_str(&format!(
"Variants\
{}", sidebar.into_inner());
}
}
fn item_ty_to_strs(ty: ItemType) -> (&'static str, &'static str) {
match ty {
ItemType::ExternCrate | ItemType::Import => ("reexports", "Re-exports"),
ItemType::Module => ("modules", "Modules"),
ItemType::Struct => ("structs", "Structs"),
ItemType::Union => ("unions", "Unions"),
ItemType::Enum => ("enums", "Enums"),
ItemType::Function => ("functions", "Functions"),
ItemType::Typedef => ("types", "Type Definitions"),
ItemType::Static => ("statics", "Statics"),
ItemType::Constant => ("constants", "Constants"),
ItemType::Trait => ("traits", "Traits"),
ItemType::Impl => ("impls", "Implementations"),
ItemType::TyMethod => ("tymethods", "Type Methods"),
ItemType::Method => ("methods", "Methods"),
ItemType::StructField => ("fields", "Struct Fields"),
ItemType::Variant => ("variants", "Variants"),
ItemType::Macro => ("macros", "Macros"),
ItemType::Primitive => ("primitives", "Primitive Types"),
ItemType::AssocType => ("associated-types", "Associated Types"),
ItemType::AssocConst => ("associated-consts", "Associated Constants"),
ItemType::ForeignType => ("foreign-types", "Foreign Types"),
ItemType::Keyword => ("keywords", "Keywords"),
ItemType::OpaqueTy => ("opaque-types", "Opaque Types"),
ItemType::ProcAttribute => ("attributes", "Attribute Macros"),
ItemType::ProcDerive => ("derives", "Derive Macros"),
ItemType::TraitAlias => ("trait-aliases", "Trait aliases"),
}
}
fn sidebar_module(buf: &mut Buffer, items: &[clean::Item]) {
let mut sidebar = String::new();
// Re-exports are handled a bit differently because they can be extern crates or imports.
if items.iter().any(|it| {
it.name.is_some()
&& (it.type_() == ItemType::ExternCrate
|| (it.type_() == ItemType::Import && !it.is_stripped()))
}) {
let (id, name) = item_ty_to_strs(ItemType::Import);
sidebar.push_str(&format!("{} ", id, name));
}
// ordering taken from item_module, reorder, where it prioritized elements in a certain order
// to print its headings
for &myty in &[
ItemType::Primitive,
ItemType::Module,
ItemType::Macro,
ItemType::Struct,
ItemType::Enum,
ItemType::Constant,
ItemType::Static,
ItemType::Trait,
ItemType::Function,
ItemType::Typedef,
ItemType::Union,
ItemType::Impl,
ItemType::TyMethod,
ItemType::Method,
ItemType::StructField,
ItemType::Variant,
ItemType::AssocType,
ItemType::AssocConst,
ItemType::ForeignType,
ItemType::Keyword,
] {
if items.iter().any(|it| !it.is_stripped() && it.type_() == myty && it.name.is_some()) {
let (id, name) = item_ty_to_strs(myty);
sidebar.push_str(&format!("{} ", id, name));
}
}
if !sidebar.is_empty() {
write!(buf, "{}
", sidebar);
}
}
fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
let mut sidebar = Buffer::new();
sidebar_assoc_items(cx, &mut sidebar, it);
if !sidebar.is_empty() {
write!(buf, "{}", sidebar.into_inner());
}
}
crate const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang";
/// Returns a list of all paths used in the type.
/// This is used to help deduplicate imported impls
/// for reexported types. If any of the contained
/// types are re-exported, we don't use the corresponding
/// entry from the js file, as inlining will have already
/// picked up the impl
fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec {
let mut out = Vec::new();
let mut visited = FxHashSet::default();
let mut work = VecDeque::new();
work.push_back(first_ty);
while let Some(ty) = work.pop_front() {
if !visited.insert(ty.clone()) {
continue;
}
match ty {
clean::Type::ResolvedPath { did, .. } => {
let get_extern = || cache.external_paths.get(&did).map(|s| s.0.clone());
let fqp = cache.exact_paths.get(&did).cloned().or_else(get_extern);
if let Some(path) = fqp {
out.push(path.join("::"));
}
}
clean::Type::Tuple(tys) => {
work.extend(tys.into_iter());
}
clean::Type::Slice(ty) => {
work.push_back(*ty);
}
clean::Type::Array(ty, _) => {
work.push_back(*ty);
}
clean::Type::RawPointer(_, ty) => {
work.push_back(*ty);
}
clean::Type::BorrowedRef { type_, .. } => {
work.push_back(*type_);
}
clean::Type::QPath { self_type, trait_, .. } => {
work.push_back(*self_type);
work.push_back(*trait_);
}
_ => {}
}
}
out
}
",
variants.join(""),
));
}
sidebar_assoc_items(cx, &mut sidebar, it);
if !sidebar.is_empty() {
write!(buf, " ");
}
sidebar_assoc_items(cx, &mut sidebar, it);
if !sidebar.is_empty() {
write!(buf, " ");
}
}
}
}
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() {
if let CtorKind::Fictive = s.struct_type {
sidebar.push_str(
"Fields\
");
}
}
sidebar_assoc_items(cx, &mut sidebar, it);
if !sidebar.is_empty() {
write!(buf, " ");
};
let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
v.iter().partition:: ");
}
}
if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
if let Some(impl_) = v
.iter()
.filter(|i| i.inner_impl().trait_.is_some())
.find(|i| i.inner_impl().trait_.def_id_full(cache) == cx.cache.deref_trait_did)
{
sidebar_deref_methods(cx, out, impl_, v);
}
let format_impls = |impls: Vec<&Impl>| {
let mut links = FxHashSet::default();
let mut ret = impls
.iter()
.filter_map(|it| {
if let Some(ref i) = it.inner_impl().trait_ {
let i_display = format!("{:#}", i.print(cx));
let out = Escape(&i_display);
let encoded = small_url_encode(format!("{:#}", i.print(cx)));
let generated = format!(
"{}{}",
encoded,
if it.inner_impl().negative_polarity { "!" } else { "" },
out
);
if links.insert(generated.clone()) { Some(generated) } else { None }
} else {
None
}
})
.collect::