//! Rustdoc's HTML rendering module.
//!
//! This modules contains the bulk of the logic necessary for rendering a
//! rustdoc `clean::Crate` instance to a set of static HTML pages. This
//! rendering process is largely driven by the `format!` syntax extension to
//! perform all I/O into files and streams.
//!
//! The rendering process is largely driven by the `Context` and `Cache`
//! structures. The cache is pre-populated by crawling the crate in question,
//! and then it is shared among the various rendering threads. The cache is meant
//! to be a fairly large structure not implementing `Clone` (because it's shared
//! among threads). The context, however, should be a lightweight structure. This
//! is cloned per-thread and contains information about what is currently being
//! rendered.
//!
//! In order to speed up rendering (mostly because of markdown rendering), the
//! rendering process has been parallelized. This parallelization is only
//! exposed through the `crate` method on the context, and then also from the
//! fact that the shared cache is stored in TLS (and must be accessed as such).
//!
//! In addition to rendering the crate itself, this module is also responsible
//! for creating the corresponding search index and source file renderings.
//! These threads are not parallelized (they haven't been a bottleneck yet), and
//! both occur before the crate is rendered.
pub(crate) mod search_index;
#[cfg(test)]
mod tests;
mod context;
mod print_item;
mod span_map;
mod write_shared;
pub(crate) use self::context::*;
pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc};
use std::collections::VecDeque;
use std::default::Default;
use std::fmt;
use std::fs;
use std::iter::Peekable;
use std::path::PathBuf;
use std::rc::Rc;
use std::str;
use std::string::ToString;
use rustc_ast_pretty::pprust;
use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::CtorKind;
use rustc_hir::def_id::DefId;
use rustc_hir::Mutability;
use rustc_middle::middle::stability;
use rustc_middle::ty;
use rustc_middle::ty::TyCtxt;
use rustc_span::{
symbol::{sym, Symbol},
BytePos, FileName, RealFileName,
};
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use crate::clean::{self, ItemId, RenderedLink, SelfTy};
use crate::error::Error;
use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape;
use crate::html::format::{
href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
print_default_space, print_generic_bounds, print_where_clause, Buffer, Ending, HrefError,
PrintWithSpace,
};
use crate::html::highlight;
use crate::html::markdown::{
HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine,
};
use crate::html::sources;
use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
use crate::scrape_examples::{CallData, CallLocation};
use crate::try_none;
use crate::DOC_RUST_LANG_ORG_CHANNEL;
/// A pair of name and its optional document.
pub(crate) type NameDoc = (String, Option(&self, serializer: S) -> Result(&self, serializer: S) -> Result{title}
",
id = kind.id(),
title = kind.name(),
);
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, ItemSection::Structs);
print_entries(f, &self.enums, ItemSection::Enums);
print_entries(f, &self.unions, ItemSection::Unions);
print_entries(f, &self.primitives, ItemSection::PrimitiveTypes);
print_entries(f, &self.traits, ItemSection::Traits);
print_entries(f, &self.macros, ItemSection::Macros);
print_entries(f, &self.attribute_macros, ItemSection::AttributeMacros);
print_entries(f, &self.derive_macros, ItemSection::DeriveMacros);
print_entries(f, &self.functions, ItemSection::Functions);
print_entries(f, &self.typedefs, ItemSection::TypeDefinitions);
print_entries(f, &self.trait_aliases, ItemSection::TraitAliases);
print_entries(f, &self.opaque_tys, ItemSection::OpaqueTypes);
print_entries(f, &self.statics, ItemSection::Statics);
print_entries(f, &self.constants, ItemSection::Constants);
}
}
fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned();
content.push_str(&format!(
"## More information\n\n\
If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).",
DOC_RUST_LANG_ORG_CHANNEL));
let mut ids = IdMap::default();
format!(
"About scraped examples
\
{}
", 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));
extra_info.push(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(Copy, 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,
}
}
}
fn write_impl_section_heading(w: &mut Buffer, title: &str, id: &str) {
write!(
w,
"\
{title}\
\
"
);
}
pub(crate) fn render_all_impls(
w: &mut Buffer,
cx: &mut Context<'_>,
containing_item: &clean::Item,
concrete: &[&Impl],
synthetic: &[&Impl],
blanket_impl: &[&Impl],
) {
let mut impls = Buffer::empty_from(w);
render_impls(cx, &mut impls, concrete, containing_item, true);
let impls = impls.into_inner();
if !impls.is_empty() {
write_impl_section_heading(w, "Trait Implementations", "trait-implementations");
write!(w, "{}", impls);
}
if !synthetic.is_empty() {
write_impl_section_heading(w, "Auto Trait Implementations", "synthetic-implementations");
w.write_str("");
render_impls(cx, w, synthetic, containing_item, false);
w.write_str("");
}
if !blanket_impl.is_empty() {
write_impl_section_heading(w, "Blanket Implementations", "blanket-implementations");
w.write_str("");
render_impls(cx, w, blanket_impl, containing_item, false);
w.write_str("");
}
}
fn render_assoc_items(
w: &mut Buffer,
cx: &mut Context<'_>,
containing_item: &clean::Item,
it: DefId,
what: AssocItemRender<'_>,
) {
let mut derefs = FxHashSet::default();
derefs.insert(it);
render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs)
}
fn render_assoc_items_inner(
w: &mut Buffer,
cx: &mut Context<'_>,
containing_item: &clean::Item,
it: DefId,
what: AssocItemRender<'_>,
derefs: &mut FxHashSet,
) {
info!("Documenting associated items of {:?}", containing_item.name);
let shared = Rc::clone(&cx.shared);
let cache = &shared.cache;
let Some(v) = cache.impls.get(&it) else { return };
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
if !non_trait.is_empty() {
let mut tmp_buf = Buffer::empty_from(w);
let (render_mode, id) = match what {
AssocItemRender::All => {
write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations");
(RenderMode::Normal, "implementations-list".to_owned())
}
AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
let id =
cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
if let Some(def_id) = type_.def_id(cx.cache()) {
cx.deref_id_map.insert(def_id, id.clone());
}
write_impl_section_heading(
&mut tmp_buf,
&format!(
"Methods from {trait_}<Target = {type_}>",
trait_ = trait_.print(cx),
type_ = type_.print(cx),
),
&id,
);
(RenderMode::ForDeref { mut_: deref_mut_ }, cx.derive_id(id))
}
};
let mut impls_buf = Buffer::empty_from(w);
for i in &non_trait {
render_impl(
&mut impls_buf,
cx,
i,
containing_item,
AssocItemLink::Anchor(None),
render_mode,
None,
&[],
ImplRenderingParameters {
show_def_docs: true,
show_default_items: true,
show_non_assoc_items: true,
toggle_open_by_default: true,
},
);
}
if !impls_buf.is_empty() {
w.push_buffer(tmp_buf);
write!(w, "", id);
w.push_buffer(impls_buf);
w.write_str("");
}
}
if !traits.is_empty() {
let deref_impl =
traits.iter().find(|t| t.trait_did() == cx.tcx().lang_items().deref_trait());
if let Some(impl_) = deref_impl {
let has_deref_mut =
traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, derefs);
}
// If we were already one level into rendering deref methods, we don't want to render
// anything after recursing into any further deref methods above.
if let AssocItemRender::DerefFor { .. } = what {
return;
}
let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
traits.into_iter().partition(|t| t.inner_impl().kind.is_auto());
let (blanket_impl, concrete): (Vec<&Impl>, _) =
concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl);
}
}
fn render_deref_methods(
w: &mut Buffer,
cx: &mut Context<'_>,
impl_: &Impl,
container_item: &clean::Item,
deref_mut: bool,
derefs: &mut FxHashSet,
) {
let cache = cx.cache();
let deref_type = impl_.inner_impl().trait_.as_ref().unwrap();
let (target, real_target) = impl_
.inner_impl()
.items
.iter()
.find_map(|item| match *item.kind {
clean::AssocTypeItem(box ref t, _) => Some(match *t {
clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
_ => (&t.type_, &t.type_),
}),
_ => None,
})
.expect("Expected associated type binding");
debug!("Render deref methods for {:#?}, target {:#?}", impl_.inner_impl().for_, target);
let what =
AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut };
if let Some(did) = target.def_id(cache) {
if let Some(type_did) = impl_.inner_impl().for_.def_id(cache) {
// `impl Deref for S`
if did == type_did || !derefs.insert(did) {
// Avoid infinite cycles
return;
}
}
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
} else if let Some(prim) = target.primitive_type() {
if let Some(&did) = cache.primitive_locations.get(&prim) {
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
}
}
}
fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool {
let self_type_opt = match *item.kind {
clean::MethodItem(ref method, _) => method.decl.self_type(),
clean::TyMethodItem(ref method) => method.decl.self_type(),
_ => None,
};
if let Some(self_ty) = self_type_opt {
let (by_mut_ref, by_box, by_value) = match self_ty {
SelfTy::SelfBorrowed(_, mutability)
| SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => {
(mutability == Mutability::Mut, false, false)
}
SelfTy::SelfExplicit(clean::Type::Path { path }) => {
(false, Some(path.def_id()) == tcx.lang_items().owned_box(), false)
}
SelfTy::SelfValue => (false, false, true),
_ => (false, false, false),
};
(deref_mut_ || !by_mut_ref) && !by_box && !by_value
} else {
false
}
}
fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
let mut out = Buffer::html();
if let Some((did, ty)) = decl.output.as_return().and_then(|t| Some((t.def_id(cx.cache())?, t)))
{
// Box has pass-through impls for Read, Write, Iterator, and Future when the
// boxed type implements one of those. We don't want to treat every Box return
// as being notably an Iterator (etc), though, so we exempt it. Pin has the same
// issue, with a pass-through impl for Future.
if Some(did) == cx.tcx().lang_items().owned_box()
|| Some(did) == cx.tcx().lang_items().pin_type()
{
return "".to_string();
}
if let Some(impls) = cx.cache().impls.get(&did) {
for i in impls {
let impl_ = i.inner_impl();
if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache())
{
// Two different types might have the same did,
// without actually being the same.
continue;
}
if let Some(trait_) = &impl_.trait_ {
let trait_did = trait_.def_id();
if cx
.cache()
.traits
.get(&trait_did)
.map_or(false, |t| t.is_notable_trait(cx.tcx()))
{
if out.is_empty() {
write!(
&mut out,
"Notable traits for {}\
",
impl_.for_.print(cx)
);
}
//use the "where" class here to make it small
write!(
&mut out,
"{}",
impl_.print(false, cx)
);
for it in &impl_.items {
if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
out.push_str(" ");
let empty_set = FxHashSet::default();
let src_link =
AssocItemLink::GotoSource(trait_did.into(), &empty_set);
assoc_type(
&mut out,
it,
&tydef.generics,
&[], // intentionally leaving out bounds
Some(&tydef.type_),
src_link,
0,
cx,
);
out.push_str(";");
}
}
}
}
}
}
}
if !out.is_empty() {
out.insert_str(
0,
"โ\
",
);
out.push_str("
");
}
out.into_inner()
}
#[derive(Clone, Copy, Debug)]
struct ImplRenderingParameters {
show_def_docs: bool,
show_default_items: bool,
/// Whether or not to show methods.
show_non_assoc_items: bool,
toggle_open_by_default: bool,
}
fn render_impl(
w: &mut Buffer,
cx: &mut Context<'_>,
i: &Impl,
parent: &clean::Item,
link: AssocItemLink<'_>,
render_mode: RenderMode,
use_absolute: Option,
aliases: &[String],
rendering_params: ImplRenderingParameters,
) {
let shared = Rc::clone(&cx.shared);
let cache = &shared.cache;
let traits = &cache.traits;
let trait_ = i.trait_did().map(|did| &traits[&did]);
let mut close_tags = String::new();
// For trait implementations, the `interesting` output contains all methods that have doc
// comments, and the `boring` output contains all methods that do not. The distinction is
// used to allow hiding the boring methods.
// `containing_item` is used for rendering stability info. If the parent is a trait impl,
// `containing_item` will the grandparent, since trait impls can't have stability attached.
fn doc_impl_item(
boring: &mut Buffer,
interesting: &mut Buffer,
cx: &mut Context<'_>,
item: &clean::Item,
parent: &clean::Item,
containing_item: &clean::Item,
link: AssocItemLink<'_>,
render_mode: RenderMode,
is_default_item: bool,
trait_: Option<&clean::Trait>,
rendering_params: ImplRenderingParameters,
) {
let item_type = item.type_();
let name = item.name.as_ref().unwrap();
let render_method_item = rendering_params.show_non_assoc_items
&& match render_mode {
RenderMode::Normal => true,
RenderMode::ForDeref { mut_: deref_mut_ } => {
should_render_item(item, deref_mut_, cx.tcx())
}
};
let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" };
let mut doc_buffer = Buffer::empty_from(boring);
let mut info_buffer = Buffer::empty_from(boring);
let mut short_documented = true;
if render_method_item {
if !is_default_item {
if let Some(t) = trait_ {
// The trait item may have been stripped so we might not
// find any documentation or stability for it.
if let Some(it) = t.items.iter().find(|i| i.name == item.name) {
// We need the stability of the item from the trait
// because impls can't have a stability.
if item.doc_value().is_some() {
document_item_info(&mut info_buffer, cx, it, Some(parent));
document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
short_documented = false;
} else {
// In case the item isn't documented,
// provide short documentation from the trait.
document_short(
&mut doc_buffer,
it,
cx,
link,
parent,
rendering_params.show_def_docs,
);
}
}
} else {
document_item_info(&mut info_buffer, cx, item, Some(parent));
if rendering_params.show_def_docs {
document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
short_documented = false;
}
}
} else {
document_short(
&mut doc_buffer,
item,
cx,
link,
parent,
rendering_params.show_def_docs,
);
}
}
let w = if short_documented && trait_.is_some() { interesting } else { boring };
let toggled = !doc_buffer.is_empty();
if toggled {
let method_toggle_class =
if item_type == ItemType::Method { " method-toggle" } else { "" };
write!(w, "", 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, render_mode);
if trait_.is_some() {
// Anchors are only used on trait impls.
write!(w, "", id);
}
w.write_str("");
render_assoc_item(
w,
item,
link.anchor(source_id.as_ref().unwrap_or(&id)),
ItemType::Impl,
cx,
render_mode,
);
w.write_str("
");
w.write_str(" ");
}
}
kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => {
let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
write!(
w,
"",
id, item_type, in_trait_class
);
render_rightside(w, cx, item, containing_item, render_mode);
if trait_.is_some() {
// Anchors are only used on trait impls.
write!(w, "", id);
}
w.write_str("");
assoc_const(
w,
item,
ty,
match kind {
clean::TyAssocConstItem(_) => None,
clean::AssocConstItem(_, default) => Some(default),
_ => unreachable!(),
},
link.anchor(if trait_.is_some() { &source_id } else { &id }),
"",
cx,
);
w.write_str("
");
w.write_str(" ");
}
clean::TyAssocTypeItem(generics, bounds) => {
let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
write!(w, "", id, item_type, in_trait_class);
if trait_.is_some() {
// Anchors are only used on trait impls.
write!(w, "", id);
}
w.write_str("");
assoc_type(
w,
item,
generics,
bounds,
None,
link.anchor(if trait_.is_some() { &source_id } else { &id }),
0,
cx,
);
w.write_str("
");
w.write_str(" ");
}
clean::AssocTypeItem(tydef, _bounds) => {
let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
write!(
w,
"",
id, item_type, in_trait_class
);
if trait_.is_some() {
// Anchors are only used on trait impls.
write!(w, "", id);
}
w.write_str("");
assoc_type(
w,
item,
&tydef.generics,
&[], // intentionally leaving out bounds
Some(tydef.item_type.as_ref().unwrap_or(&tydef.type_)),
link.anchor(if trait_.is_some() { &source_id } else { &id }),
0,
cx,
);
w.write_str("
");
w.write_str(" ");
}
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,
render_mode,
false,
trait_,
rendering_params,
);
}
fn render_default_items(
boring: &mut Buffer,
interesting: &mut Buffer,
cx: &mut Context<'_>,
t: &clean::Trait,
i: &clean::Impl,
parent: &clean::Item,
containing_item: &clean::Item,
render_mode: RenderMode,
rendering_params: ImplRenderingParameters,
) {
for trait_item in &t.items {
// Skip over any default trait items that are impossible to call
// (e.g. if it has a `Self: Sized` bound on an unsized type).
if let Some(impl_def_id) = parent.item_id.as_def_id()
&& let Some(trait_item_def_id) = trait_item.item_id.as_def_id()
&& cx.tcx().is_impossible_method((impl_def_id, trait_item_def_id))
{
continue;
}
let n = trait_item.name;
if i.items.iter().any(|m| m.name == n) {
continue;
}
let did = i.trait_.as_ref().unwrap().def_id();
let provided_methods = i.provided_trait_methods(cx.tcx());
let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods);
doc_impl_item(
boring,
interesting,
cx,
trait_item,
parent,
containing_item,
assoc_link,
render_mode,
true,
Some(t),
rendering_params,
);
}
}
// If we've implemented a trait, then also emit documentation for all
// default items which weren't overridden in the implementation block.
// We don't emit documentation for default items if they appear in the
// Implementations on Foreign Types or Implementors sections.
if rendering_params.show_default_items {
if let Some(t) = trait_ {
render_default_items(
&mut default_impl_items,
&mut impl_items,
cx,
t,
i.inner_impl(),
&i.impl_item,
parent,
render_mode,
rendering_params,
);
}
}
if render_mode == RenderMode::Normal {
let toggled = !(impl_items.is_empty() && default_impl_items.is_empty());
if toggled {
close_tags.insert_str(0, "");
write!(
w,
"",
if rendering_params.toggle_open_by_default { " open" } else { "" }
);
write!(w, "")
}
render_impl_summary(
w,
cx,
i,
parent,
parent,
rendering_params.show_def_docs,
use_absolute,
aliases,
);
if toggled {
write!(w, "
")
}
if let Some(ref dox) = i.impl_item.collapsed_doc_value() {
if trait_.is_none() && i.inner_impl().items.is_empty() {
w.write_str(
"\
This impl block contains no items.
",
);
}
write!(
w,
"{}",
Markdown {
content: &*dox,
links: &i.impl_item.links(cx),
ids: &mut cx.id_map,
error_codes: cx.shared.codes,
edition: cx.shared.edition(),
playground: &cx.shared.playground,
heading_offset: HeadingOffset::H4
}
.into_string()
);
}
}
if !default_impl_items.is_empty() || !impl_items.is_empty() {
w.write_str("");
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) ยท source".
fn render_rightside(
w: &mut Buffer,
cx: &Context<'_>,
item: &clean::Item,
containing_item: &clean::Item,
render_mode: RenderMode,
) {
let tcx = cx.tcx();
// FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
// this condition.
let (const_stability, const_stable_since) = match render_mode {
RenderMode::Normal => (item.const_stability(tcx), containing_item.const_stable_since(tcx)),
RenderMode::ForDeref { .. } => (None, None),
};
let src_href = cx.src_href(item);
let has_src_ref = src_href.is_some();
let mut rightside = Buffer::new();
let has_stability = render_stability_since_raw_with_extra(
&mut rightside,
item.stable_since(tcx),
const_stability,
containing_item.stable_since(tcx),
const_stable_since,
if has_src_ref { "" } else { " rightside" },
);
if let Some(l) = src_href {
if has_stability {
write!(rightside, " ยท source", l)
} else {
write!(rightside, "source", l)
}
}
if has_stability && has_src_ref {
write!(w, "{}", rightside.into_inner());
} else {
w.push_buffer(rightside);
}
}
pub(crate) fn render_impl_summary(
w: &mut Buffer,
cx: &mut Context<'_>,
i: &Impl,
parent: &clean::Item,
containing_item: &clean::Item,
show_def_docs: bool,
use_absolute: Option,
// 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 inner_impl = i.inner_impl();
let id = cx.derive_id(get_id_for_impl(&inner_impl.for_, inner_impl.trait_.as_ref(), cx));
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, RenderMode::Normal);
write!(w, "", id);
write!(w, "");
if let Some(use_absolute) = use_absolute {
write!(w, "{}", inner_impl.print(use_absolute, cx));
if show_def_docs {
for it in &inner_impl.items {
if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
w.write_str(" ");
assoc_type(
w,
it,
&tydef.generics,
&[], // intentionally leaving out bounds
Some(&tydef.type_),
AssocItemLink::Anchor(None),
0,
cx,
);
w.write_str(";");
}
}
}
} else {
write!(w, "{}", inner_impl.print(false, cx));
}
write!(w, "
");
let is_trait = 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) {
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(" ");
}
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::>()
}
// 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
}
}
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 = FxHashSet::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 FxHashSet,
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) {
if let Some(type_did) = impl_.inner_impl().for_.def_id(c) {
// `impl Deref for S`
if did == type_did || !derefs.insert(did) {
// Avoid infinite cycles
return;
}
}
}
let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
let inner_impl = target
.def_id(c)
.or_else(|| {
target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned())
})
.and_then(|did| c.impls.get(&did));
if let Some(impls) = inner_impl {
debug!("found inner_impl: {:?}", impls);
let mut 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) {
if let Some(target_impls) = c.impls.get(&target_did) {
if let Some(target_deref_impl) = target_impls.iter().find(|i| {
i.inner_impl()
.trait_
.as_ref()
.map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
.unwrap_or(false)
}) {
sidebar_deref_methods(
cx,
out,
target_deref_impl,
target_impls,
derefs,
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.struct_type {
CtorKind::Fictive => {
print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter());
}
CtorKind::Fn => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"),
CtorKind::Const => {}
}
}
sidebar_assoc_items(cx, &mut sidebar, it);
if !sidebar.is_empty() {
write!(buf, "{} ", sidebar.into_inner());
}
}
fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String {
match trait_ {
Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))),
None => small_url_encode(format!("impl-{:#}", for_.print(cx))),
}
}
fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
match *item.kind {
clean::ItemKind::ImplItem(ref i) => {
i.trait_.as_ref().map(|trait_| {
// Alternative format produces no URLs,
// so this parameter does nothing.
(format!("{:#}", i.for_.print(cx)), get_id_for_impl(&i.for_, Some(trait_), cx))
})
}
_ => None,
}
}
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`.
pub(crate) fn get_filtered_impls_for_reference<'a>(
shared: &'a Rc>,
it: &clean::Item,
) -> (Vec<&'a Impl>, Vec<&'a Impl>, Vec<&'a Impl>) {
let def_id = it.item_id.expect_def_id();
// If the reference primitive is somehow not defined, exit early.
let Some(v) = shared.cache.impls.get(&def_id) else { return (Vec::new(), Vec::new(), Vec::new()) };
// Since there is no "direct implementation" on the reference primitive type, we filter out
// every implementation which isn't a trait implementation.
let traits = v.iter().filter(|i| i.inner_impl().trait_.is_some());
let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
traits.partition(|t| t.inner_impl().kind.is_auto());
let (blanket_impl, concrete): (Vec<&Impl>, _) =
concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
// Now we keep only references over full generic types.
let concrete: Vec<_> = concrete
.into_iter()
.filter(|t| match t.inner_impl().for_ {
clean::Type::BorrowedRef { ref type_, .. } => type_.is_full_generic(),
_ => false,
})
.collect();
(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,
PrimitiveTypes,
Modules,
Macros,
Structs,
Enums,
Constants,
Statics,
Traits,
Functions,
TypeDefinitions,
Unions,
Implementations,
TypeMethods,
Methods,
StructFields,
Variants,
AssociatedTypes,
AssociatedConstants,
ForeignTypes,
Keywords,
OpaqueTypes,
AttributeMacros,
DeriveMacros,
TraitAliases,
}
impl ItemSection {
const ALL: &'static [Self] = {
use ItemSection::*;
// NOTE: The order here affects the order in the UI.
&[
Reexports,
PrimitiveTypes,
Modules,
Macros,
Structs,
Enums,
Constants,
Statics,
Traits,
Functions,
TypeDefinitions,
Unions,
Implementations,
TypeMethods,
Methods,
StructFields,
Variants,
AssociatedTypes,
AssociatedConstants,
ForeignTypes,
Keywords,
OpaqueTypes,
AttributeMacros,
DeriveMacros,
TraitAliases,
]
};
fn id(self) -> &'static str {
match self {
Self::Reexports => "reexports",
Self::Modules => "modules",
Self::Structs => "structs",
Self::Unions => "unions",
Self::Enums => "enums",
Self::Functions => "functions",
Self::TypeDefinitions => "types",
Self::Statics => "statics",
Self::Constants => "constants",
Self::Traits => "traits",
Self::Implementations => "impls",
Self::TypeMethods => "tymethods",
Self::Methods => "methods",
Self::StructFields => "fields",
Self::Variants => "variants",
Self::Macros => "macros",
Self::PrimitiveTypes => "primitives",
Self::AssociatedTypes => "associated-types",
Self::AssociatedConstants => "associated-consts",
Self::ForeignTypes => "foreign-types",
Self::Keywords => "keywords",
Self::OpaqueTypes => "opaque-types",
Self::AttributeMacros => "attributes",
Self::DeriveMacros => "derives",
Self::TraitAliases => "trait-aliases",
}
}
fn name(self) -> &'static str {
match self {
Self::Reexports => "Re-exports",
Self::Modules => "Modules",
Self::Structs => "Structs",
Self::Unions => "Unions",
Self::Enums => "Enums",
Self::Functions => "Functions",
Self::TypeDefinitions => "Type Definitions",
Self::Statics => "Statics",
Self::Constants => "Constants",
Self::Traits => "Traits",
Self::Implementations => "Implementations",
Self::TypeMethods => "Type Methods",
Self::Methods => "Methods",
Self::StructFields => "Struct Fields",
Self::Variants => "Variants",
Self::Macros => "Macros",
Self::PrimitiveTypes => "Primitive Types",
Self::AssociatedTypes => "Associated Types",
Self::AssociatedConstants => "Associated Constants",
Self::ForeignTypes => "Foreign Types",
Self::Keywords => "Keywords",
Self::OpaqueTypes => "Opaque Types",
Self::AttributeMacros => "Attribute Macros",
Self::DeriveMacros => "Derive Macros",
Self::TraitAliases => "Trait Aliases",
}
}
}
fn item_ty_to_section(ty: ItemType) -> ItemSection {
match ty {
ItemType::ExternCrate | ItemType::Import => ItemSection::Reexports,
ItemType::Module => ItemSection::Modules,
ItemType::Struct => ItemSection::Structs,
ItemType::Union => ItemSection::Unions,
ItemType::Enum => ItemSection::Enums,
ItemType::Function => ItemSection::Functions,
ItemType::Typedef => ItemSection::TypeDefinitions,
ItemType::Static => ItemSection::Statics,
ItemType::Constant => ItemSection::Constants,
ItemType::Trait => ItemSection::Traits,
ItemType::Impl => ItemSection::Implementations,
ItemType::TyMethod => ItemSection::TypeMethods,
ItemType::Method => ItemSection::Methods,
ItemType::StructField => ItemSection::StructFields,
ItemType::Variant => ItemSection::Variants,
ItemType::Macro => ItemSection::Macros,
ItemType::Primitive => ItemSection::PrimitiveTypes,
ItemType::AssocType => ItemSection::AssociatedTypes,
ItemType::AssocConst => ItemSection::AssociatedConstants,
ItemType::ForeignType => ItemSection::ForeignTypes,
ItemType::Keyword => ItemSection::Keywords,
ItemType::OpaqueTy => ItemSection::OpaqueTypes,
ItemType::ProcAttribute => ItemSection::AttributeMacros,
ItemType::ProcDerive => ItemSection::DeriveMacros,
ItemType::TraitAlias => ItemSection::TraitAliases,
}
}
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());
}
}
pub(crate) const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang";
/// Returns a list of all paths used in the type.
/// This is used to help deduplicate imported impls
/// for reexported types. If any of the contained
/// types are re-exported, we don't use the corresponding
/// entry from the js file, as inlining will have already
/// picked up the impl
fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec {
let mut out = Vec::new();
let mut visited = FxHashSet::default();
let mut work = VecDeque::new();
let mut process_path = |did: DefId| {
let get_extern = || cache.external_paths.get(&did).map(|s| s.0.clone());
let fqp = cache.exact_paths.get(&did).cloned().or_else(get_extern);
if let Some(path) = fqp {
out.push(join_with_double_colon(&path));
}
};
work.push_back(first_ty);
while let Some(ty) = work.pop_front() {
if !visited.insert(ty.clone()) {
continue;
}
match ty {
clean::Type::Path { path } => process_path(path.def_id()),
clean::Type::Tuple(tys) => {
work.extend(tys.into_iter());
}
clean::Type::Slice(ty) => {
work.push_back(*ty);
}
clean::Type::Array(ty, _) => {
work.push_back(*ty);
}
clean::Type::RawPointer(_, ty) => {
work.push_back(*ty);
}
clean::Type::BorrowedRef { type_, .. } => {
work.push_back(*type_);
}
clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => {
work.push_back(self_type);
process_path(trait_.def_id());
}
_ => {}
}
}
out
}
const MAX_FULL_EXAMPLES: usize = 5;
const NUM_VISIBLE_LINES: usize = 10;
/// Generates the HTML for example call locations generated via the --scrape-examples flag.
fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item) {
let tcx = cx.tcx();
let def_id = item.item_id.expect_def_id();
let key = tcx.def_path_hash(def_id);
let Some(call_locations) = cx.shared.call_locations.get(&key) else { return };
// Generate a unique ID so users can link to this section for a given method
let id = cx.id_map.derive("scraped-examples");
write!(
w,
"\
\
\
Examples found in repository\
?\
",
root_path = cx.root_path(),
id = id
);
// Create a URL to a particular location in a reverse-dependency's source file
let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) {
let (line_lo, line_hi) = loc.call_expr.line_span;
let (anchor, title) = if line_lo == line_hi {
((line_lo + 1).to_string(), format!("line {}", line_lo + 1))
} else {
(
format!("{}-{}", line_lo + 1, line_hi + 1),
format!("lines {}-{}", line_lo + 1, line_hi + 1),
)
};
let url = format!("{}{}#{}", cx.root_path(), call_data.url, anchor);
(url, title)
};
// Generate the HTML for a single example, being the title and code block
let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| -> bool {
let contents = match fs::read_to_string(&path) {
Ok(contents) => contents,
Err(err) => {
let span = item.span(tcx).map_or(rustc_span::DUMMY_SP, |span| span.inner());
tcx.sess
.span_err(span, &format!("failed to read file {}: {}", path.display(), err));
return false;
}
};
// To reduce file sizes, we only want to embed the source code needed to understand the example, not
// the entire file. So we find the smallest byte range that covers all items enclosing examples.
assert!(!call_data.locations.is_empty());
let min_loc =
call_data.locations.iter().min_by_key(|loc| loc.enclosing_item.byte_span.0).unwrap();
let byte_min = min_loc.enclosing_item.byte_span.0;
let line_min = min_loc.enclosing_item.line_span.0;
let max_loc =
call_data.locations.iter().max_by_key(|loc| loc.enclosing_item.byte_span.1).unwrap();
let byte_max = max_loc.enclosing_item.byte_span.1;
let line_max = max_loc.enclosing_item.line_span.1;
// The output code is limited to that byte range.
let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)];
// The call locations need to be updated to reflect that the size of the program has changed.
// Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point.
let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data
.locations
.iter()
.map(|loc| {
let (byte_lo, byte_hi) = loc.call_ident.byte_span;
let (line_lo, line_hi) = loc.call_expr.line_span;
let byte_range = (byte_lo - byte_min, byte_hi - byte_min);
let line_range = (line_lo - line_min, line_hi - line_min);
let (line_url, line_title) = link_to_loc(call_data, loc);
(byte_range, (line_range, line_url, line_title))
})
.unzip();
let (_, init_url, init_title) = &line_ranges[0];
let needs_expansion = line_max - line_min > NUM_VISIBLE_LINES;
let locations_encoded = serde_json::to_string(&line_ranges).unwrap();
write!(
w,
"\
\
{name} ({title})\
\
",
expanded_cls = if needs_expansion { "" } else { "expanded" },
name = call_data.display_name,
url = init_url,
title = init_title,
// The locations are encoded as a data attribute, so they can be read
// later by the JS for interactions.
locations = Escape(&locations_encoded)
);
if line_ranges.len() > 1 {
write!(w, r#"≺ ≻"#);
}
if needs_expansion {
write!(w, r#" "#);
}
// Look for the example file in the source map if it exists, otherwise return a dummy span
let file_span = (|| {
let source_map = tcx.sess.source_map();
let crate_src = tcx.sess.local_crate_source_file.as_ref()?;
let abs_crate_src = crate_src.canonicalize().ok()?;
let crate_root = abs_crate_src.parent()?.parent()?;
let rel_path = path.strip_prefix(crate_root).ok()?;
let files = source_map.files();
let file = files.iter().find(|file| match &file.name {
FileName::Real(RealFileName::LocalPath(other_path)) => rel_path == other_path,
_ => false,
})?;
Some(rustc_span::Span::with_root_ctxt(
file.start_pos + BytePos(byte_min),
file.start_pos + BytePos(byte_max),
))
})()
.unwrap_or(rustc_span::DUMMY_SP);
// The root path is the inverse of Context::current
let root_path = vec!["../"; cx.current.len() - 1].join("");
let mut decoration_info = FxHashMap::default();
decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
decoration_info.insert("highlight", byte_ranges);
sources::print_src(
w,
contents_subset,
file_span,
cx,
&root_path,
highlight::DecorationInfo(decoration_info),
sources::SourceContext::Embedded { offset: line_min },
);
write!(w, "");
true
};
// The call locations are output in sequence, so that sequence needs to be determined.
// Ideally the most "relevant" examples would be shown first, but there's no general algorithm
// for determining relevance. Instead, we prefer the smallest examples being likely the easiest to
// understand at a glance.
let ordered_locations = {
let sort_criterion = |(_, call_data): &(_, &CallData)| {
// Use the first location because that's what the user will see initially
let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
hi - lo
};
let mut locs = call_locations.iter().collect::>();
locs.sort_by_key(sort_criterion);
locs
};
let mut it = ordered_locations.into_iter().peekable();
// An example may fail to write if its source can't be read for some reason, so this method
// continues iterating until a write succeeds
let write_and_skip_failure = |w: &mut Buffer, it: &mut Peekable<_>| {
while let Some(example) = it.next() {
if write_example(&mut *w, example) {
break;
}
}
};
// Write just one example that's visible by default in the method's description.
write_and_skip_failure(w, &mut it);
// Then add the remaining examples in a hidden section.
if it.peek().is_some() {
write!(
w,
"\
\
More examples\
\
Hide additional examples\
\
\
"
);
// Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
// make the page arbitrarily huge!
for _ in 0..MAX_FULL_EXAMPLES {
write_and_skip_failure(w, &mut it);
}
// For the remaining examples, generate a containing links to the source files.
if it.peek().is_some() {
write!(w, r#"Additional examples can be found in:
"#);
it.for_each(|(_, call_data)| {
let (url, _) = link_to_loc(call_data, &call_data.locations[0]);
write!(
w,
r#"- {name}
"#,
url = url,
name = call_data.display_name
);
});
write!(w, "
");
}
write!(w, "
");
}
write!(w, " ");
}