mirror of
https://github.com/rust-lang/rust.git
synced 2025-11-18 13:26:58 +00:00
Proper support for cross-crate recursive const stability checks ~~Stacked on top of https://github.com/rust-lang/rust/pull/132492; only the last three commits are new.~~ In a crate without `staged_api` but with `-Zforce-unstable-if-unmarked`, we now subject all functions marked with `#[rustc_const_stable_indirect]` to recursive const stability checks. We require an opt-in so that by default, a crate can be built with `-Zforce-unstable-if-unmarked` and use nightly features as usual. This property is recorded in the crate metadata so when a `staged_api` crate calls such a function, it sees the `#[rustc_const_stable_indirect]` and allows it to be exposed on stable. This, finally, will let us expose `const fn` from hashbrown on stable. The second commit makes const stability more like regular stability: via `check_missing_const_stability`, we ensure that all publicly reachable functions have a const stability attribute -- both in `staged_api` crates and `-Zforce-unstable-if-unmarked` crates. To achieve this, we move around the stability computation so that const stability is computed after regular stability is done. This lets us access the final result of the regular stability computation, which we use so that `const fn` can inherit the regular stability (but only if that is "unstable"). Fortunately, this lets us get rid of an `Option` in `ConstStability`. This is the last PR that I have planned in this series. r? `@compiler-errors`
2638 lines
95 KiB
Rust
2638 lines
95 KiB
Rust
//! 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 ordered_json;
|
|
mod print_item;
|
|
pub(crate) mod sidebar;
|
|
mod sorted_template;
|
|
mod span_map;
|
|
mod type_layout;
|
|
mod write_shared;
|
|
|
|
use std::collections::VecDeque;
|
|
use std::fmt::{self, Write};
|
|
use std::iter::Peekable;
|
|
use std::path::PathBuf;
|
|
use std::rc::Rc;
|
|
use std::{fs, str};
|
|
|
|
use rinja::Template;
|
|
use rustc_attr::{ConstStability, DeprecatedSince, Deprecation, StabilityLevel, StableSince};
|
|
use rustc_data_structures::captures::Captures;
|
|
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
|
|
use rustc_hir::Mutability;
|
|
use rustc_hir::def_id::{DefId, DefIdSet};
|
|
use rustc_middle::ty::print::PrintTraitRefExt;
|
|
use rustc_middle::ty::{self, TyCtxt};
|
|
use rustc_session::RustcVersion;
|
|
use rustc_span::symbol::{Symbol, sym};
|
|
use rustc_span::{BytePos, DUMMY_SP, FileName, RealFileName};
|
|
use serde::ser::SerializeMap;
|
|
use serde::{Serialize, Serializer};
|
|
use tracing::{debug, info};
|
|
|
|
pub(crate) use self::context::*;
|
|
pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources};
|
|
pub(crate) use self::write_shared::*;
|
|
use crate::clean::{self, ItemId, RenderedLink};
|
|
use crate::error::Error;
|
|
use crate::formats::Impl;
|
|
use crate::formats::cache::Cache;
|
|
use crate::formats::item_type::ItemType;
|
|
use crate::html::escape::Escape;
|
|
use crate::html::format::{
|
|
Buffer, Ending, HrefError, PrintWithSpace, display_fn, href, join_with_double_colon,
|
|
print_abi_with_space, print_constness_with_space, print_default_space, print_generic_bounds,
|
|
print_where_clause, visibility_print_with_space,
|
|
};
|
|
use crate::html::markdown::{
|
|
HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine,
|
|
};
|
|
use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
|
|
use crate::html::{highlight, sources};
|
|
use crate::scrape_examples::{CallData, CallLocation};
|
|
use crate::{DOC_RUST_LANG_ORG_CHANNEL, try_none};
|
|
|
|
pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ {
|
|
crate::html::format::display_fn(move |f| {
|
|
if !v.ends_with('/') && !v.is_empty() { write!(f, "{v}/") } else { f.write_str(v) }
|
|
})
|
|
}
|
|
|
|
/// Specifies whether rendering directly implemented trait items or ones from a certain Deref
|
|
/// impl.
|
|
pub(crate) enum AssocItemRender<'a> {
|
|
All,
|
|
DerefFor { trait_: &'a clean::Path, type_: &'a clean::Type, deref_mut_: bool },
|
|
}
|
|
|
|
/// For different handling of associated items from the Deref target of a type rather than the type
|
|
/// itself.
|
|
#[derive(Copy, Clone, PartialEq)]
|
|
pub(crate) enum RenderMode {
|
|
Normal,
|
|
ForDeref { mut_: bool },
|
|
}
|
|
|
|
// Helper structs for rendering items/sidebars and carrying along contextual
|
|
// information
|
|
|
|
/// Struct representing one entry in the JS search index. These are all emitted
|
|
/// by hand to a large JS file at the end of cache-creation.
|
|
#[derive(Debug)]
|
|
pub(crate) struct IndexItem {
|
|
pub(crate) ty: ItemType,
|
|
pub(crate) defid: Option<DefId>,
|
|
pub(crate) name: Symbol,
|
|
pub(crate) path: String,
|
|
pub(crate) desc: String,
|
|
pub(crate) parent: Option<DefId>,
|
|
pub(crate) parent_idx: Option<isize>,
|
|
pub(crate) exact_path: Option<String>,
|
|
pub(crate) impl_id: Option<DefId>,
|
|
pub(crate) search_type: Option<IndexItemFunctionType>,
|
|
pub(crate) aliases: Box<[Symbol]>,
|
|
pub(crate) deprecation: Option<Deprecation>,
|
|
}
|
|
|
|
/// A type used for the search index.
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub(crate) struct RenderType {
|
|
id: Option<RenderTypeId>,
|
|
generics: Option<Vec<RenderType>>,
|
|
bindings: Option<Vec<(RenderTypeId, Vec<RenderType>)>>,
|
|
}
|
|
|
|
impl RenderType {
|
|
// Types are rendered as lists of lists, because that's pretty compact.
|
|
// The contents of the lists are always integers in self-terminating hex
|
|
// form, handled by `RenderTypeId::write_to_string`, so no commas are
|
|
// needed to separate the items.
|
|
pub fn write_to_string(&self, string: &mut String) {
|
|
fn write_optional_id(id: Option<RenderTypeId>, string: &mut String) {
|
|
// 0 is a sentinel, everything else is one-indexed
|
|
match id {
|
|
Some(id) => id.write_to_string(string),
|
|
None => string.push('`'),
|
|
}
|
|
}
|
|
// Either just the type id, or `{type, generics, bindings?}`
|
|
// where generics is a list of types,
|
|
// and bindings is a list of `{id, typelist}` pairs.
|
|
if self.generics.is_some() || self.bindings.is_some() {
|
|
string.push('{');
|
|
write_optional_id(self.id, string);
|
|
string.push('{');
|
|
for generic in &self.generics.as_deref().unwrap_or_default()[..] {
|
|
generic.write_to_string(string);
|
|
}
|
|
string.push('}');
|
|
if self.bindings.is_some() {
|
|
string.push('{');
|
|
for binding in &self.bindings.as_deref().unwrap_or_default()[..] {
|
|
string.push('{');
|
|
binding.0.write_to_string(string);
|
|
string.push('{');
|
|
for constraint in &binding.1[..] {
|
|
constraint.write_to_string(string);
|
|
}
|
|
string.push_str("}}");
|
|
}
|
|
string.push('}');
|
|
}
|
|
string.push('}');
|
|
} else {
|
|
write_optional_id(self.id, string);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub(crate) enum RenderTypeId {
|
|
DefId(DefId),
|
|
Primitive(clean::PrimitiveType),
|
|
AssociatedType(Symbol),
|
|
Index(isize),
|
|
Mut,
|
|
}
|
|
|
|
impl RenderTypeId {
|
|
pub fn write_to_string(&self, string: &mut String) {
|
|
let id: i32 = match &self {
|
|
// 0 is a sentinel, everything else is one-indexed
|
|
// concrete type
|
|
RenderTypeId::Index(idx) if *idx >= 0 => (idx + 1isize).try_into().unwrap(),
|
|
// generic type parameter
|
|
RenderTypeId::Index(idx) => (*idx).try_into().unwrap(),
|
|
_ => panic!("must convert render types to indexes before serializing"),
|
|
};
|
|
search_index::encode::write_vlqhex_to_string(id, string);
|
|
}
|
|
}
|
|
|
|
/// Full type of functions/methods in the search index.
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub(crate) struct IndexItemFunctionType {
|
|
inputs: Vec<RenderType>,
|
|
output: Vec<RenderType>,
|
|
where_clause: Vec<Vec<RenderType>>,
|
|
param_names: Vec<Symbol>,
|
|
}
|
|
|
|
impl IndexItemFunctionType {
|
|
pub fn write_to_string<'a>(
|
|
&'a self,
|
|
string: &mut String,
|
|
backref_queue: &mut VecDeque<&'a IndexItemFunctionType>,
|
|
) {
|
|
assert!(backref_queue.len() <= 16);
|
|
// If we couldn't figure out a type, just write 0,
|
|
// which is encoded as `` ` `` (see RenderTypeId::write_to_string).
|
|
let has_missing = self
|
|
.inputs
|
|
.iter()
|
|
.chain(self.output.iter())
|
|
.any(|i| i.id.is_none() && i.generics.is_none());
|
|
if has_missing {
|
|
string.push('`');
|
|
} else if let Some(idx) = backref_queue.iter().position(|other| *other == self) {
|
|
// The backref queue has 16 items, so backrefs use
|
|
// a single hexit, disjoint from the ones used for numbers.
|
|
string.push(
|
|
char::try_from('0' as u32 + u32::try_from(idx).unwrap())
|
|
.expect("last possible value is '?'"),
|
|
);
|
|
} else {
|
|
backref_queue.push_front(self);
|
|
if backref_queue.len() > 16 {
|
|
backref_queue.pop_back();
|
|
}
|
|
string.push('{');
|
|
match &self.inputs[..] {
|
|
[one] if one.generics.is_none() && one.bindings.is_none() => {
|
|
one.write_to_string(string);
|
|
}
|
|
_ => {
|
|
string.push('{');
|
|
for item in &self.inputs[..] {
|
|
item.write_to_string(string);
|
|
}
|
|
string.push('}');
|
|
}
|
|
}
|
|
match &self.output[..] {
|
|
[] if self.where_clause.is_empty() => {}
|
|
[one] if one.generics.is_none() && one.bindings.is_none() => {
|
|
one.write_to_string(string);
|
|
}
|
|
_ => {
|
|
string.push('{');
|
|
for item in &self.output[..] {
|
|
item.write_to_string(string);
|
|
}
|
|
string.push('}');
|
|
}
|
|
}
|
|
for constraint in &self.where_clause {
|
|
if let [one] = &constraint[..]
|
|
&& one.generics.is_none()
|
|
&& one.bindings.is_none()
|
|
{
|
|
one.write_to_string(string);
|
|
} else {
|
|
string.push('{');
|
|
for item in &constraint[..] {
|
|
item.write_to_string(string);
|
|
}
|
|
string.push('}');
|
|
}
|
|
}
|
|
string.push('}');
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct StylePath {
|
|
/// The path to the theme
|
|
pub(crate) path: PathBuf,
|
|
}
|
|
|
|
impl StylePath {
|
|
pub(crate) fn basename(&self) -> Result<String, Error> {
|
|
Ok(try_none!(try_none!(self.path.file_stem(), &self.path).to_str(), &self.path).to_string())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
|
struct ItemEntry {
|
|
url: String,
|
|
name: String,
|
|
}
|
|
|
|
impl ItemEntry {
|
|
fn new(mut url: String, name: String) -> ItemEntry {
|
|
while url.starts_with('/') {
|
|
url.remove(0);
|
|
}
|
|
ItemEntry { url, name }
|
|
}
|
|
}
|
|
|
|
impl ItemEntry {
|
|
pub(crate) fn print(&self) -> impl fmt::Display + '_ {
|
|
crate::html::format::display_fn(move |f| {
|
|
write!(f, "<a href=\"{}\">{}</a>", self.url, Escape(&self.name))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for ItemEntry {
|
|
fn partial_cmp(&self, other: &ItemEntry) -> Option<::std::cmp::Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl Ord for ItemEntry {
|
|
fn cmp(&self, other: &ItemEntry) -> ::std::cmp::Ordering {
|
|
self.name.cmp(&other.name)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct AllTypes {
|
|
structs: FxIndexSet<ItemEntry>,
|
|
enums: FxIndexSet<ItemEntry>,
|
|
unions: FxIndexSet<ItemEntry>,
|
|
primitives: FxIndexSet<ItemEntry>,
|
|
traits: FxIndexSet<ItemEntry>,
|
|
macros: FxIndexSet<ItemEntry>,
|
|
functions: FxIndexSet<ItemEntry>,
|
|
type_aliases: FxIndexSet<ItemEntry>,
|
|
statics: FxIndexSet<ItemEntry>,
|
|
constants: FxIndexSet<ItemEntry>,
|
|
attribute_macros: FxIndexSet<ItemEntry>,
|
|
derive_macros: FxIndexSet<ItemEntry>,
|
|
trait_aliases: FxIndexSet<ItemEntry>,
|
|
}
|
|
|
|
impl AllTypes {
|
|
fn new() -> AllTypes {
|
|
let new_set = |cap| FxIndexSet::with_capacity_and_hasher(cap, Default::default());
|
|
AllTypes {
|
|
structs: new_set(100),
|
|
enums: new_set(100),
|
|
unions: new_set(100),
|
|
primitives: new_set(26),
|
|
traits: new_set(100),
|
|
macros: new_set(100),
|
|
functions: new_set(100),
|
|
type_aliases: new_set(100),
|
|
statics: new_set(100),
|
|
constants: new_set(100),
|
|
attribute_macros: new_set(100),
|
|
derive_macros: new_set(100),
|
|
trait_aliases: new_set(100),
|
|
}
|
|
}
|
|
|
|
fn append(&mut self, item_name: String, item_type: &ItemType) {
|
|
let mut url: Vec<_> = item_name.split("::").skip(1).collect();
|
|
if let Some(name) = url.pop() {
|
|
let new_url = format!("{}/{item_type}.{name}.html", url.join("/"));
|
|
url.push(name);
|
|
let name = url.join("::");
|
|
match *item_type {
|
|
ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::TypeAlias => self.type_aliases.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::ProcAttribute => {
|
|
self.attribute_macros.insert(ItemEntry::new(new_url, name))
|
|
}
|
|
ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)),
|
|
ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)),
|
|
_ => true,
|
|
};
|
|
}
|
|
}
|
|
|
|
fn item_sections(&self) -> FxHashSet<ItemSection> {
|
|
let mut sections = FxHashSet::default();
|
|
|
|
if !self.structs.is_empty() {
|
|
sections.insert(ItemSection::Structs);
|
|
}
|
|
if !self.enums.is_empty() {
|
|
sections.insert(ItemSection::Enums);
|
|
}
|
|
if !self.unions.is_empty() {
|
|
sections.insert(ItemSection::Unions);
|
|
}
|
|
if !self.primitives.is_empty() {
|
|
sections.insert(ItemSection::PrimitiveTypes);
|
|
}
|
|
if !self.traits.is_empty() {
|
|
sections.insert(ItemSection::Traits);
|
|
}
|
|
if !self.macros.is_empty() {
|
|
sections.insert(ItemSection::Macros);
|
|
}
|
|
if !self.functions.is_empty() {
|
|
sections.insert(ItemSection::Functions);
|
|
}
|
|
if !self.type_aliases.is_empty() {
|
|
sections.insert(ItemSection::TypeAliases);
|
|
}
|
|
if !self.statics.is_empty() {
|
|
sections.insert(ItemSection::Statics);
|
|
}
|
|
if !self.constants.is_empty() {
|
|
sections.insert(ItemSection::Constants);
|
|
}
|
|
if !self.attribute_macros.is_empty() {
|
|
sections.insert(ItemSection::AttributeMacros);
|
|
}
|
|
if !self.derive_macros.is_empty() {
|
|
sections.insert(ItemSection::DeriveMacros);
|
|
}
|
|
if !self.trait_aliases.is_empty() {
|
|
sections.insert(ItemSection::TraitAliases);
|
|
}
|
|
|
|
sections
|
|
}
|
|
|
|
fn print(self, f: &mut Buffer) {
|
|
fn print_entries(f: &mut Buffer, e: &FxIndexSet<ItemEntry>, kind: ItemSection) {
|
|
if !e.is_empty() {
|
|
let mut e: Vec<&ItemEntry> = e.iter().collect();
|
|
e.sort();
|
|
write!(
|
|
f,
|
|
"<h3 id=\"{id}\">{title}</h3><ul class=\"all-items\">",
|
|
id = kind.id(),
|
|
title = kind.name(),
|
|
);
|
|
|
|
for s in e.iter() {
|
|
write!(f, "<li>{}</li>", s.print());
|
|
}
|
|
|
|
f.write_str("</ul>");
|
|
}
|
|
}
|
|
|
|
f.write_str("<h1>List of all items</h1>");
|
|
// 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.type_aliases, ItemSection::TypeAliases);
|
|
print_entries(f, &self.trait_aliases, ItemSection::TraitAliases);
|
|
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]({DOC_RUST_LANG_ORG_CHANNEL}/rustdoc/scraped-examples.html)."
|
|
));
|
|
|
|
let mut ids = IdMap::default();
|
|
format!(
|
|
"<div class=\"main-heading\">\
|
|
<h1>About scraped examples</h1>\
|
|
</div>\
|
|
<div>{}</div>",
|
|
Markdown {
|
|
content: &content,
|
|
links: &[],
|
|
ids: &mut ids,
|
|
error_codes: shared.codes,
|
|
edition: shared.edition(),
|
|
playground: &shared.playground,
|
|
heading_offset: HeadingOffset::H1,
|
|
}
|
|
.into_string()
|
|
)
|
|
}
|
|
|
|
fn document<'a, 'cx: 'a>(
|
|
cx: &'a mut Context<'cx>,
|
|
item: &'a clean::Item,
|
|
parent: Option<&'a clean::Item>,
|
|
heading_offset: HeadingOffset,
|
|
) -> impl fmt::Display + 'a + Captures<'cx> {
|
|
if let Some(ref name) = item.name {
|
|
info!("Documenting {name}");
|
|
}
|
|
|
|
display_fn(move |f| {
|
|
document_item_info(cx, item, parent).render_into(f).unwrap();
|
|
if parent.is_none() {
|
|
write!(f, "{}", document_full_collapsible(item, cx, heading_offset))
|
|
} else {
|
|
write!(f, "{}", document_full(item, cx, heading_offset))
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Render md_text as markdown.
|
|
fn render_markdown<'a, 'cx: 'a>(
|
|
cx: &'a mut Context<'cx>,
|
|
md_text: &'a str,
|
|
links: Vec<RenderedLink>,
|
|
heading_offset: HeadingOffset,
|
|
) -> impl fmt::Display + 'a + Captures<'cx> {
|
|
display_fn(move |f| {
|
|
write!(
|
|
f,
|
|
"<div class=\"docblock\">{}</div>",
|
|
Markdown {
|
|
content: md_text,
|
|
links: &links,
|
|
ids: &mut cx.id_map,
|
|
error_codes: cx.shared.codes,
|
|
edition: cx.shared.edition(),
|
|
playground: &cx.shared.playground,
|
|
heading_offset,
|
|
}
|
|
.into_string()
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Writes a documentation block containing only the first paragraph of the documentation. If the
|
|
/// docs are longer, a "Read more" link is appended to the end.
|
|
fn document_short<'a, 'cx: 'a>(
|
|
item: &'a clean::Item,
|
|
cx: &'a mut Context<'cx>,
|
|
link: AssocItemLink<'a>,
|
|
parent: &'a clean::Item,
|
|
show_def_docs: bool,
|
|
) -> impl fmt::Display + 'a + Captures<'cx> {
|
|
display_fn(move |f| {
|
|
document_item_info(cx, item, Some(parent)).render_into(f).unwrap();
|
|
if !show_def_docs {
|
|
return Ok(());
|
|
}
|
|
let s = item.doc_value();
|
|
if !s.is_empty() {
|
|
let (mut summary_html, has_more_content) =
|
|
MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
|
|
|
|
if has_more_content {
|
|
let link = format!(" <a{}>Read more</a>", assoc_href_attr(item, link, cx));
|
|
|
|
if let Some(idx) = summary_html.rfind("</p>") {
|
|
summary_html.insert_str(idx, &link);
|
|
} else {
|
|
summary_html.push_str(&link);
|
|
}
|
|
}
|
|
|
|
write!(f, "<div class='docblock'>{summary_html}</div>")?;
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn document_full_collapsible<'a, 'cx: 'a>(
|
|
item: &'a clean::Item,
|
|
cx: &'a mut Context<'cx>,
|
|
heading_offset: HeadingOffset,
|
|
) -> impl fmt::Display + 'a + Captures<'cx> {
|
|
document_full_inner(item, cx, true, heading_offset)
|
|
}
|
|
|
|
fn document_full<'a, 'cx: 'a>(
|
|
item: &'a clean::Item,
|
|
cx: &'a mut Context<'cx>,
|
|
heading_offset: HeadingOffset,
|
|
) -> impl fmt::Display + 'a + Captures<'cx> {
|
|
document_full_inner(item, cx, false, heading_offset)
|
|
}
|
|
|
|
fn document_full_inner<'a, 'cx: 'a>(
|
|
item: &'a clean::Item,
|
|
cx: &'a mut Context<'cx>,
|
|
is_collapsible: bool,
|
|
heading_offset: HeadingOffset,
|
|
) -> impl fmt::Display + 'a + Captures<'cx> {
|
|
display_fn(move |f| {
|
|
if let Some(s) = item.opt_doc_value() {
|
|
debug!("Doc block: =====\n{s}\n=====");
|
|
if is_collapsible {
|
|
write!(
|
|
f,
|
|
"<details class=\"toggle top-doc\" open>\
|
|
<summary class=\"hideme\">\
|
|
<span>Expand description</span>\
|
|
</summary>{}</details>",
|
|
render_markdown(cx, &s, item.links(cx), heading_offset)
|
|
)?;
|
|
} else {
|
|
write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?;
|
|
}
|
|
}
|
|
|
|
let kind = match &item.kind {
|
|
clean::ItemKind::StrippedItem(box kind) | kind => kind,
|
|
};
|
|
|
|
if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
|
|
render_call_locations(f, cx, item);
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "item_info.html")]
|
|
struct ItemInfo {
|
|
items: Vec<ShortItemInfo>,
|
|
}
|
|
/// Add extra information about an item such as:
|
|
///
|
|
/// * Stability
|
|
/// * Deprecated
|
|
/// * Required features (through the `doc_cfg` feature)
|
|
fn document_item_info(
|
|
cx: &mut Context<'_>,
|
|
item: &clean::Item,
|
|
parent: Option<&clean::Item>,
|
|
) -> ItemInfo {
|
|
let items = short_item_info(item, cx, parent);
|
|
ItemInfo { items }
|
|
}
|
|
|
|
fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> {
|
|
let cfg = match (&item.cfg, parent.and_then(|p| p.cfg.as_ref())) {
|
|
(Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
|
|
(cfg, _) => cfg.as_deref().cloned(),
|
|
};
|
|
|
|
debug!(
|
|
"Portability {name:?} {item_cfg:?} (parent: {parent:?}) - {parent_cfg:?} = {cfg:?}",
|
|
name = item.name,
|
|
item_cfg = item.cfg,
|
|
parent_cfg = parent.and_then(|p| p.cfg.as_ref()),
|
|
);
|
|
|
|
Some(cfg?.render_long_html())
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "short_item_info.html")]
|
|
enum ShortItemInfo {
|
|
/// A message describing the deprecation of this item
|
|
Deprecation {
|
|
message: String,
|
|
},
|
|
/// The feature corresponding to an unstable item, and optionally
|
|
/// a tracking issue URL and number.
|
|
Unstable {
|
|
feature: String,
|
|
tracking: Option<(String, u32)>,
|
|
},
|
|
Portability {
|
|
message: String,
|
|
},
|
|
}
|
|
|
|
/// Render the stability, deprecation and portability information that is displayed at the top of
|
|
/// the item's documentation.
|
|
fn short_item_info(
|
|
item: &clean::Item,
|
|
cx: &mut Context<'_>,
|
|
parent: Option<&clean::Item>,
|
|
) -> Vec<ShortItemInfo> {
|
|
let mut extra_info = vec![];
|
|
|
|
if let Some(depr @ Deprecation { note, since, suggestion: _ }) = item.deprecation(cx.tcx()) {
|
|
// We display deprecation messages for #[deprecated], but only display
|
|
// the future-deprecation messages for rustc versions.
|
|
let mut message = match since {
|
|
DeprecatedSince::RustcVersion(version) => {
|
|
if depr.is_in_effect() {
|
|
format!("Deprecated since {version}")
|
|
} else {
|
|
format!("Deprecating in {version}")
|
|
}
|
|
}
|
|
DeprecatedSince::Future => String::from("Deprecating in a future version"),
|
|
DeprecatedSince::NonStandard(since) => {
|
|
format!("Deprecated since {}", Escape(since.as_str()))
|
|
}
|
|
DeprecatedSince::Unspecified | DeprecatedSince::Err => String::from("Deprecated"),
|
|
};
|
|
|
|
if let Some(note) = note {
|
|
let note = note.as_str();
|
|
let html = MarkdownItemInfo(note, &mut cx.id_map);
|
|
message.push_str(": ");
|
|
message.push_str(&html.into_string());
|
|
}
|
|
extra_info.push(ShortItemInfo::Deprecation { message });
|
|
}
|
|
|
|
// Render unstable items. But don't render "rustc_private" crates (internal compiler crates).
|
|
// Those crates are permanently unstable so it makes no sense to render "unstable" everywhere.
|
|
if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item
|
|
.stability(cx.tcx())
|
|
.as_ref()
|
|
.filter(|stab| stab.feature != sym::rustc_private)
|
|
.map(|stab| (stab.level, stab.feature))
|
|
{
|
|
let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue)
|
|
{
|
|
Some((url.clone(), issue.get()))
|
|
} else {
|
|
None
|
|
};
|
|
extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking });
|
|
}
|
|
|
|
if let Some(message) = portability(item, parent) {
|
|
extra_info.push(ShortItemInfo::Portability { message });
|
|
}
|
|
|
|
extra_info
|
|
}
|
|
|
|
// Render the list of items inside one of the sections "Trait Implementations",
|
|
// "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages).
|
|
pub(crate) fn render_impls(
|
|
cx: &mut Context<'_>,
|
|
mut w: impl Write,
|
|
impls: &[&Impl],
|
|
containing_item: &clean::Item,
|
|
toggle_open_by_default: bool,
|
|
) {
|
|
let tcx = cx.tcx();
|
|
let mut rendered_impls = impls
|
|
.iter()
|
|
.map(|i| {
|
|
let did = i.trait_did().unwrap();
|
|
let provided_trait_methods = i.inner_impl().provided_trait_methods(tcx);
|
|
let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods);
|
|
let mut buffer = Buffer::new();
|
|
render_impl(
|
|
&mut buffer,
|
|
cx,
|
|
i,
|
|
containing_item,
|
|
assoc_link,
|
|
RenderMode::Normal,
|
|
None,
|
|
&[],
|
|
ImplRenderingParameters {
|
|
show_def_docs: true,
|
|
show_default_items: true,
|
|
show_non_assoc_items: true,
|
|
toggle_open_by_default,
|
|
},
|
|
);
|
|
buffer.into_inner()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
rendered_impls.sort();
|
|
w.write_str(&rendered_impls.join("")).unwrap();
|
|
}
|
|
|
|
/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
|
|
fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
|
|
let name = it.name.unwrap();
|
|
let item_type = it.type_();
|
|
|
|
let href = match link {
|
|
AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{id}")),
|
|
AssocItemLink::Anchor(None) => Some(format!("#{item_type}.{name}")),
|
|
AssocItemLink::GotoSource(did, provided_methods) => {
|
|
// We're creating a link from the implementation of an associated item to its
|
|
// declaration in the trait declaration.
|
|
let item_type = match item_type {
|
|
// For historical but not technical reasons, the item type of methods in
|
|
// trait declarations depends on whether the method is required (`TyMethod`) or
|
|
// provided (`Method`).
|
|
ItemType::Method | ItemType::TyMethod => {
|
|
if provided_methods.contains(&name) {
|
|
ItemType::Method
|
|
} else {
|
|
ItemType::TyMethod
|
|
}
|
|
}
|
|
// For associated types and constants, no such distinction exists.
|
|
item_type => item_type,
|
|
};
|
|
|
|
match href(did.expect_def_id(), cx) {
|
|
Ok((url, ..)) => Some(format!("{url}#{item_type}.{name}")),
|
|
// The link is broken since it points to an external crate that wasn't documented.
|
|
// Do not create any link in such case. This is better than falling back to a
|
|
// dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
|
|
// (that used to happen in older versions). Indeed, in most cases this dummy would
|
|
// coincide with the `id`. However, it would not always do so.
|
|
// In general, this dummy would be incorrect:
|
|
// If the type with the trait impl also had an inherent impl with an assoc. item of
|
|
// the *same* name as this impl item, the dummy would link to that one even though
|
|
// those two items are distinct!
|
|
// In this scenario, the actual `id` of this impl item would be
|
|
// `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
|
|
Err(HrefError::DocumentationNotBuilt) => None,
|
|
Err(_) => Some(format!("#{item_type}.{name}")),
|
|
}
|
|
}
|
|
};
|
|
|
|
// If there is no `href` for the reason explained above, simply do not render it which is valid:
|
|
// https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
|
|
href.map(|href| format!(" href=\"{href}\"")).unwrap_or_default()
|
|
}
|
|
|
|
fn assoc_const(
|
|
w: &mut Buffer,
|
|
it: &clean::Item,
|
|
generics: &clean::Generics,
|
|
ty: &clean::Type,
|
|
default: Option<&clean::ConstantKind>,
|
|
link: AssocItemLink<'_>,
|
|
indent: usize,
|
|
cx: &Context<'_>,
|
|
) {
|
|
let tcx = cx.tcx();
|
|
write!(
|
|
w,
|
|
"{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}",
|
|
indent = " ".repeat(indent),
|
|
vis = visibility_print_with_space(it, cx),
|
|
href = assoc_href_attr(it, link, cx),
|
|
name = it.name.as_ref().unwrap(),
|
|
generics = generics.print(cx),
|
|
ty = ty.print(cx),
|
|
);
|
|
if let Some(default) = default {
|
|
w.write_str(" = ");
|
|
|
|
// FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the
|
|
// hood which adds noisy underscores and a type suffix to number literals.
|
|
// This hurts readability in this context especially when more complex expressions
|
|
// are involved and it doesn't add much of value.
|
|
// Find a way to print constants here without all that jazz.
|
|
write!(w, "{}", Escape(&default.value(tcx).unwrap_or_else(|| default.expr(tcx))));
|
|
}
|
|
write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
|
|
}
|
|
|
|
fn assoc_type(
|
|
w: &mut Buffer,
|
|
it: &clean::Item,
|
|
generics: &clean::Generics,
|
|
bounds: &[clean::GenericBound],
|
|
default: Option<&clean::Type>,
|
|
link: AssocItemLink<'_>,
|
|
indent: usize,
|
|
cx: &Context<'_>,
|
|
) {
|
|
write!(
|
|
w,
|
|
"{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
|
|
indent = " ".repeat(indent),
|
|
vis = visibility_print_with_space(it, cx),
|
|
href = assoc_href_attr(it, link, cx),
|
|
name = it.name.as_ref().unwrap(),
|
|
generics = generics.print(cx),
|
|
);
|
|
if !bounds.is_empty() {
|
|
write!(w, ": {}", print_generic_bounds(bounds, cx))
|
|
}
|
|
// Render the default before the where-clause which aligns with the new recommended style. See #89122.
|
|
if let Some(default) = default {
|
|
write!(w, " = {}", default.print(cx))
|
|
}
|
|
write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
|
|
}
|
|
|
|
fn assoc_method(
|
|
w: &mut Buffer,
|
|
meth: &clean::Item,
|
|
g: &clean::Generics,
|
|
d: &clean::FnDecl,
|
|
link: AssocItemLink<'_>,
|
|
parent: ItemType,
|
|
cx: &mut Context<'_>,
|
|
render_mode: RenderMode,
|
|
) {
|
|
let tcx = cx.tcx();
|
|
let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item");
|
|
let name = meth.name.as_ref().unwrap();
|
|
let vis = visibility_print_with_space(meth, cx).to_string();
|
|
let defaultness = print_default_space(meth.is_default());
|
|
// FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
|
|
// this condition.
|
|
let constness = match render_mode {
|
|
RenderMode::Normal => print_constness_with_space(
|
|
&header.constness,
|
|
meth.stable_since(tcx),
|
|
meth.const_stability(tcx),
|
|
),
|
|
RenderMode::ForDeref { .. } => "",
|
|
};
|
|
let asyncness = header.asyncness.print_with_space();
|
|
let safety = header.safety.print_with_space();
|
|
let abi = print_abi_with_space(header.abi).to_string();
|
|
let href = assoc_href_attr(meth, link, cx);
|
|
|
|
// NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
|
|
let generics_len = format!("{:#}", g.print(cx)).len();
|
|
let mut header_len = "fn ".len()
|
|
+ vis.len()
|
|
+ defaultness.len()
|
|
+ constness.len()
|
|
+ asyncness.len()
|
|
+ safety.len()
|
|
+ abi.len()
|
|
+ name.as_str().len()
|
|
+ generics_len;
|
|
|
|
let notable_traits = notable_traits_button(&d.output, cx);
|
|
|
|
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
|
|
header_len += 4;
|
|
let indent_str = " ";
|
|
write!(w, "{}", render_attributes_in_pre(meth, indent_str, cx));
|
|
(4, indent_str, Ending::NoNewline)
|
|
} else {
|
|
render_attributes_in_code(w, meth, cx);
|
|
(0, "", Ending::Newline)
|
|
};
|
|
w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
|
|
write!(
|
|
w,
|
|
"{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \
|
|
<a{href} class=\"fn\">{name}</a>{generics}{decl}{notable_traits}{where_clause}",
|
|
indent = indent_str,
|
|
vis = vis,
|
|
defaultness = defaultness,
|
|
constness = constness,
|
|
asyncness = asyncness,
|
|
safety = safety,
|
|
abi = abi,
|
|
href = href,
|
|
name = name,
|
|
generics = g.print(cx),
|
|
decl = d.full_print(header_len, indent, cx),
|
|
notable_traits = notable_traits.unwrap_or_default(),
|
|
where_clause = print_where_clause(g, cx, indent, end_newline),
|
|
);
|
|
}
|
|
|
|
/// Writes a span containing the versions at which an item became stable and/or const-stable. For
|
|
/// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would
|
|
/// write a span containing "1.0.0 (const: 1.45.0)".
|
|
///
|
|
/// Returns `true` if a stability annotation was rendered.
|
|
///
|
|
/// Stability and const-stability are considered separately. If the item is unstable, no version
|
|
/// will be written. If the item is const-unstable, "const: unstable" will be appended to the
|
|
/// span, with a link to the tracking issue if present. If an item's stability or const-stability
|
|
/// version matches the version of its enclosing item, that version will be omitted.
|
|
///
|
|
/// Note that it is possible for an unstable function to be const-stable. In that case, the span
|
|
/// will include the const-stable version, but no stable version will be emitted, as a natural
|
|
/// consequence of the above rules.
|
|
fn render_stability_since_raw_with_extra(
|
|
w: &mut Buffer,
|
|
stable_version: Option<StableSince>,
|
|
const_stability: Option<ConstStability>,
|
|
extra_class: &str,
|
|
) -> bool {
|
|
let mut title = String::new();
|
|
let mut stability = String::new();
|
|
|
|
if let Some(version) = stable_version.and_then(|version| since_to_string(&version)) {
|
|
stability.push_str(&version);
|
|
title.push_str(&format!("Stable since Rust version {version}"));
|
|
}
|
|
|
|
let const_title_and_stability = match const_stability {
|
|
Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. }) => {
|
|
since_to_string(&since)
|
|
.map(|since| (format!("const since {since}"), format!("const: {since}")))
|
|
}
|
|
Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => {
|
|
if stable_version.is_none() {
|
|
// don't display const unstable if entirely unstable
|
|
None
|
|
} else {
|
|
let unstable = if let Some(n) = issue {
|
|
format!(
|
|
"<a \
|
|
href=\"https://github.com/rust-lang/rust/issues/{n}\" \
|
|
title=\"Tracking issue for {feature}\"\
|
|
>unstable</a>"
|
|
)
|
|
} else {
|
|
String::from("unstable")
|
|
};
|
|
|
|
Some((String::from("const unstable"), format!("const: {unstable}")))
|
|
}
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
if let Some((const_title, const_stability)) = const_title_and_stability {
|
|
if !title.is_empty() {
|
|
title.push_str(&format!(", {const_title}"));
|
|
} else {
|
|
title.push_str(&const_title);
|
|
}
|
|
|
|
if !stability.is_empty() {
|
|
stability.push_str(&format!(" ({const_stability})"));
|
|
} else {
|
|
stability.push_str(&const_stability);
|
|
}
|
|
}
|
|
|
|
if !stability.is_empty() {
|
|
write!(w, r#"<span class="since{extra_class}" title="{title}">{stability}</span>"#);
|
|
}
|
|
|
|
!stability.is_empty()
|
|
}
|
|
|
|
fn since_to_string(since: &StableSince) -> Option<String> {
|
|
match since {
|
|
StableSince::Version(since) => Some(since.to_string()),
|
|
StableSince::Current => Some(RustcVersion::CURRENT.to_string()),
|
|
StableSince::Err => None,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn render_stability_since_raw(
|
|
w: &mut Buffer,
|
|
ver: Option<StableSince>,
|
|
const_stability: Option<ConstStability>,
|
|
) -> bool {
|
|
render_stability_since_raw_with_extra(w, ver, const_stability, "")
|
|
}
|
|
|
|
fn render_assoc_item(
|
|
w: &mut Buffer,
|
|
item: &clean::Item,
|
|
link: AssocItemLink<'_>,
|
|
parent: ItemType,
|
|
cx: &mut Context<'_>,
|
|
render_mode: RenderMode,
|
|
) {
|
|
match &item.kind {
|
|
clean::StrippedItem(..) => {}
|
|
clean::TyMethodItem(m) => {
|
|
assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
|
|
}
|
|
clean::MethodItem(m, _) => {
|
|
assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
|
|
}
|
|
clean::TyAssocConstItem(generics, ty) => assoc_const(
|
|
w,
|
|
item,
|
|
generics,
|
|
ty,
|
|
None,
|
|
link,
|
|
if parent == ItemType::Trait { 4 } else { 0 },
|
|
cx,
|
|
),
|
|
clean::AssocConstItem(ci) => assoc_const(
|
|
w,
|
|
item,
|
|
&ci.generics,
|
|
&ci.type_,
|
|
Some(&ci.kind),
|
|
link,
|
|
if parent == ItemType::Trait { 4 } else { 0 },
|
|
cx,
|
|
),
|
|
clean::TyAssocTypeItem(ref generics, ref bounds) => assoc_type(
|
|
w,
|
|
item,
|
|
generics,
|
|
bounds,
|
|
None,
|
|
link,
|
|
if parent == ItemType::Trait { 4 } else { 0 },
|
|
cx,
|
|
),
|
|
clean::AssocTypeItem(ref ty, ref bounds) => assoc_type(
|
|
w,
|
|
item,
|
|
&ty.generics,
|
|
bounds,
|
|
Some(ty.item_type.as_ref().unwrap_or(&ty.type_)),
|
|
link,
|
|
if parent == ItemType::Trait { 4 } else { 0 },
|
|
cx,
|
|
),
|
|
_ => panic!("render_assoc_item called on non-associated-item"),
|
|
}
|
|
}
|
|
|
|
// When an attribute is rendered inside a `<pre>` tag, it is formatted using
|
|
// a whitespace prefix and newline.
|
|
fn render_attributes_in_pre<'a, 'tcx: 'a>(
|
|
it: &'a clean::Item,
|
|
prefix: &'a str,
|
|
cx: &'a Context<'tcx>,
|
|
) -> impl fmt::Display + Captures<'a> + Captures<'tcx> {
|
|
crate::html::format::display_fn(move |f| {
|
|
for a in it.attributes(cx.tcx(), cx.cache(), false) {
|
|
writeln!(f, "{prefix}{a}")?;
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
// When an attribute is rendered inside a <code> tag, it is formatted using
|
|
// a div to produce a newline after it.
|
|
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
|
|
for attr in it.attributes(cx.tcx(), cx.cache(), false) {
|
|
write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
enum AssocItemLink<'a> {
|
|
Anchor(Option<&'a str>),
|
|
GotoSource(ItemId, &'a FxIndexSet<Symbol>),
|
|
}
|
|
|
|
impl<'a> AssocItemLink<'a> {
|
|
fn anchor(&self, id: &'a str) -> Self {
|
|
match *self {
|
|
AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(id)),
|
|
ref other => *other,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn write_section_heading(
|
|
w: &mut impl fmt::Write,
|
|
title: &str,
|
|
id: &str,
|
|
extra_class: Option<&str>,
|
|
extra: impl fmt::Display,
|
|
) {
|
|
let (extra_class, whitespace) = match extra_class {
|
|
Some(extra) => (extra, " "),
|
|
None => ("", ""),
|
|
};
|
|
write!(
|
|
w,
|
|
"<h2 id=\"{id}\" class=\"{extra_class}{whitespace}section-header\">\
|
|
{title}\
|
|
<a href=\"#{id}\" class=\"anchor\">§</a>\
|
|
</h2>{extra}",
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
fn write_impl_section_heading(w: &mut impl fmt::Write, title: &str, id: &str) {
|
|
write_section_heading(w, title, id, None, "")
|
|
}
|
|
|
|
pub(crate) fn render_all_impls(
|
|
mut w: impl Write,
|
|
cx: &mut Context<'_>,
|
|
containing_item: &clean::Item,
|
|
concrete: &[&Impl],
|
|
synthetic: &[&Impl],
|
|
blanket_impl: &[&Impl],
|
|
) {
|
|
let mut impls = Buffer::html();
|
|
render_impls(cx, &mut impls, concrete, containing_item, true);
|
|
let impls = impls.into_inner();
|
|
if !impls.is_empty() {
|
|
write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations");
|
|
write!(w, "<div id=\"trait-implementations-list\">{impls}</div>").unwrap();
|
|
}
|
|
|
|
if !synthetic.is_empty() {
|
|
write_impl_section_heading(
|
|
&mut w,
|
|
"Auto Trait Implementations",
|
|
"synthetic-implementations",
|
|
);
|
|
w.write_str("<div id=\"synthetic-implementations-list\">").unwrap();
|
|
render_impls(cx, &mut w, synthetic, containing_item, false);
|
|
w.write_str("</div>").unwrap();
|
|
}
|
|
|
|
if !blanket_impl.is_empty() {
|
|
write_impl_section_heading(&mut w, "Blanket Implementations", "blanket-implementations");
|
|
w.write_str("<div id=\"blanket-implementations-list\">").unwrap();
|
|
render_impls(cx, &mut w, blanket_impl, containing_item, false);
|
|
w.write_str("</div>").unwrap();
|
|
}
|
|
}
|
|
|
|
fn render_assoc_items<'a, 'cx: 'a>(
|
|
cx: &'a mut Context<'cx>,
|
|
containing_item: &'a clean::Item,
|
|
it: DefId,
|
|
what: AssocItemRender<'a>,
|
|
) -> impl fmt::Display + 'a + Captures<'cx> {
|
|
let mut derefs = DefIdSet::default();
|
|
derefs.insert(it);
|
|
display_fn(move |f| {
|
|
render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn render_assoc_items_inner(
|
|
mut w: &mut dyn fmt::Write,
|
|
cx: &mut Context<'_>,
|
|
containing_item: &clean::Item,
|
|
it: DefId,
|
|
what: AssocItemRender<'_>,
|
|
derefs: &mut DefIdSet,
|
|
) {
|
|
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 close_tags = <Vec<&str>>::with_capacity(1);
|
|
let mut tmp_buf = Buffer::html();
|
|
let (render_mode, id, class_html) = 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))));
|
|
let derived_id = cx.derive_id(&id);
|
|
tmp_buf.write_str("<details class=\"toggle big-toggle\" open><summary>");
|
|
close_tags.push("</details>");
|
|
write_impl_section_heading(
|
|
&mut tmp_buf,
|
|
&format!(
|
|
"<span>Methods from {trait_}<Target = {type_}></span>",
|
|
trait_ = trait_.print(cx),
|
|
type_ = type_.print(cx),
|
|
),
|
|
&id,
|
|
);
|
|
tmp_buf.write_str("</summary>");
|
|
if let Some(def_id) = type_.def_id(cx.cache()) {
|
|
cx.deref_id_map.insert(def_id, id);
|
|
}
|
|
(RenderMode::ForDeref { mut_: deref_mut_ }, derived_id, r#" class="impl-items""#)
|
|
}
|
|
};
|
|
let mut impls_buf = Buffer::html();
|
|
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() {
|
|
write!(
|
|
w,
|
|
"{}<div id=\"{id}\"{class_html}>{}</div>",
|
|
tmp_buf.into_inner(),
|
|
impls_buf.into_inner()
|
|
)
|
|
.unwrap();
|
|
for tag in close_tags.into_iter().rev() {
|
|
w.write_str(tag).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
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(&mut w, cx, impl_, containing_item, has_deref_mut, derefs);
|
|
}
|
|
|
|
// If we were already one level into rendering deref methods, we don't want to render
|
|
// 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(
|
|
mut w: impl Write,
|
|
cx: &mut Context<'_>,
|
|
impl_: &Impl,
|
|
container_item: &clean::Item,
|
|
deref_mut: bool,
|
|
derefs: &mut DefIdSet,
|
|
) {
|
|
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::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_),
|
|
_ => (&t.type_, &t.type_),
|
|
}),
|
|
_ => None,
|
|
})
|
|
.expect("Expected associated type binding");
|
|
debug!(
|
|
"Render deref methods for {for_:#?}, target {target:#?}",
|
|
for_ = impl_.inner_impl().for_
|
|
);
|
|
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<Target = S> for S`
|
|
if did == type_did || !derefs.insert(did) {
|
|
// Avoid infinite cycles
|
|
return;
|
|
}
|
|
}
|
|
render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
|
|
} else if let Some(prim) = target.primitive_type() {
|
|
if let Some(&did) = cache.primitive_locations.get(&prim) {
|
|
render_assoc_items_inner(&mut 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.receiver_type(),
|
|
clean::TyMethodItem(ref method) => method.decl.receiver_type(),
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(self_ty) = self_type_opt {
|
|
let (by_mut_ref, by_box, by_value) = match *self_ty {
|
|
clean::Type::BorrowedRef { mutability, .. } => {
|
|
(mutability == Mutability::Mut, false, false)
|
|
}
|
|
clean::Type::Path { ref path } => {
|
|
(false, Some(path.def_id()) == tcx.lang_items().owned_box(), false)
|
|
}
|
|
clean::Type::SelfTy => (false, false, true),
|
|
_ => (false, false, false),
|
|
};
|
|
|
|
(deref_mut_ || !by_mut_ref) && !by_box && !by_value
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String> {
|
|
let mut has_notable_trait = false;
|
|
|
|
if ty.is_unit() {
|
|
// Very common fast path.
|
|
return None;
|
|
}
|
|
|
|
let did = ty.def_id(cx.cache())?;
|
|
|
|
// 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 None;
|
|
}
|
|
|
|
if let Some(impls) = cx.cache().impls.get(&did) {
|
|
for i in impls {
|
|
let impl_ = i.inner_impl();
|
|
if impl_.polarity != ty::ImplPolarity::Positive {
|
|
continue;
|
|
}
|
|
|
|
if !ty.is_doc_subtype_of(&impl_.for_, 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).is_some_and(|t| t.is_notable_trait(cx.tcx())) {
|
|
has_notable_trait = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if has_notable_trait {
|
|
cx.types_with_notable_traits.insert(ty.clone());
|
|
Some(format!(
|
|
" <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
|
|
ty = Escape(&format!("{:#}", ty.print(cx))),
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
|
|
let mut out = Buffer::html();
|
|
|
|
let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this");
|
|
|
|
let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this");
|
|
|
|
for i in impls {
|
|
let impl_ = i.inner_impl();
|
|
if impl_.polarity != ty::ImplPolarity::Positive {
|
|
continue;
|
|
}
|
|
|
|
if !ty.is_doc_subtype_of(&impl_.for_, 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).is_some_and(|t| t.is_notable_trait(cx.tcx())) {
|
|
if out.is_empty() {
|
|
write!(
|
|
&mut out,
|
|
"<h3>Notable traits for <code>{}</code></h3>\
|
|
<pre><code>",
|
|
impl_.for_.print(cx)
|
|
);
|
|
}
|
|
|
|
write!(&mut out, "<div class=\"where\">{}</div>", impl_.print(false, cx));
|
|
for it in &impl_.items {
|
|
if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind {
|
|
out.push_str("<div class=\"where\"> ");
|
|
let empty_set = FxIndexSet::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(";</div>");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if out.is_empty() {
|
|
out.write_str("</code></pre>");
|
|
}
|
|
|
|
(format!("{:#}", ty.print(cx)), out.into_inner())
|
|
}
|
|
|
|
pub(crate) fn notable_traits_json<'a>(
|
|
tys: impl Iterator<Item = &'a clean::Type>,
|
|
cx: &Context<'_>,
|
|
) -> String {
|
|
let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect();
|
|
mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2));
|
|
struct NotableTraitsMap(Vec<(String, String)>);
|
|
impl Serialize for NotableTraitsMap {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
|
for item in &self.0 {
|
|
map.serialize_entry(&item.0, &item.1)?;
|
|
}
|
|
map.end()
|
|
}
|
|
}
|
|
serde_json::to_string(&NotableTraitsMap(mp))
|
|
.expect("serialize (string, string) -> json object cannot fail")
|
|
}
|
|
|
|
#[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<bool>,
|
|
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 = <Vec<&str>>::with_capacity(2);
|
|
|
|
// 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,
|
|
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_empty() {
|
|
document_item_info(cx, it, Some(parent))
|
|
.render_into(&mut info_buffer)
|
|
.unwrap();
|
|
write!(
|
|
&mut doc_buffer,
|
|
"{}",
|
|
document_full(item, cx, HeadingOffset::H5)
|
|
);
|
|
short_documented = false;
|
|
} else {
|
|
// In case the item isn't documented,
|
|
// provide short documentation from the trait.
|
|
write!(
|
|
&mut doc_buffer,
|
|
"{}",
|
|
document_short(
|
|
it,
|
|
cx,
|
|
link,
|
|
parent,
|
|
rendering_params.show_def_docs,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
document_item_info(cx, item, Some(parent))
|
|
.render_into(&mut info_buffer)
|
|
.unwrap();
|
|
if rendering_params.show_def_docs {
|
|
write!(&mut doc_buffer, "{}", document_full(item, cx, HeadingOffset::H5));
|
|
short_documented = false;
|
|
}
|
|
}
|
|
} else {
|
|
write!(
|
|
&mut doc_buffer,
|
|
"{}",
|
|
document_short(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.is_method() { " method-toggle" } else { "" };
|
|
write!(w, "<details class=\"toggle{method_toggle_class}\" open><summary>");
|
|
}
|
|
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 == *name).unwrap_or(false))
|
|
})
|
|
.map(|item| format!("{}.{name}", item.type_()));
|
|
write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
|
|
render_rightside(w, cx, item, render_mode);
|
|
if trait_.is_some() {
|
|
// Anchors are only used on trait impls.
|
|
write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>");
|
|
}
|
|
w.write_str("<h4 class=\"code-header\">");
|
|
render_assoc_item(
|
|
w,
|
|
item,
|
|
link.anchor(source_id.as_ref().unwrap_or(&id)),
|
|
ItemType::Impl,
|
|
cx,
|
|
render_mode,
|
|
);
|
|
w.write_str("</h4></section>");
|
|
}
|
|
}
|
|
clean::TyAssocConstItem(ref generics, ref ty) => {
|
|
let source_id = format!("{item_type}.{name}");
|
|
let id = cx.derive_id(&source_id);
|
|
write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
|
|
render_rightside(w, cx, item, render_mode);
|
|
if trait_.is_some() {
|
|
// Anchors are only used on trait impls.
|
|
write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>");
|
|
}
|
|
w.write_str("<h4 class=\"code-header\">");
|
|
assoc_const(
|
|
w,
|
|
item,
|
|
generics,
|
|
ty,
|
|
None,
|
|
link.anchor(if trait_.is_some() { &source_id } else { &id }),
|
|
0,
|
|
cx,
|
|
);
|
|
w.write_str("</h4></section>");
|
|
}
|
|
clean::AssocConstItem(ci) => {
|
|
let source_id = format!("{item_type}.{name}");
|
|
let id = cx.derive_id(&source_id);
|
|
write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
|
|
render_rightside(w, cx, item, render_mode);
|
|
if trait_.is_some() {
|
|
// Anchors are only used on trait impls.
|
|
write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>");
|
|
}
|
|
w.write_str("<h4 class=\"code-header\">");
|
|
assoc_const(
|
|
w,
|
|
item,
|
|
&ci.generics,
|
|
&ci.type_,
|
|
Some(&ci.kind),
|
|
link.anchor(if trait_.is_some() { &source_id } else { &id }),
|
|
0,
|
|
cx,
|
|
);
|
|
w.write_str("</h4></section>");
|
|
}
|
|
clean::TyAssocTypeItem(ref generics, ref bounds) => {
|
|
let source_id = format!("{item_type}.{name}");
|
|
let id = cx.derive_id(&source_id);
|
|
write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
|
|
render_rightside(w, cx, item, render_mode);
|
|
if trait_.is_some() {
|
|
// Anchors are only used on trait impls.
|
|
write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>");
|
|
}
|
|
w.write_str("<h4 class=\"code-header\">");
|
|
assoc_type(
|
|
w,
|
|
item,
|
|
generics,
|
|
bounds,
|
|
None,
|
|
link.anchor(if trait_.is_some() { &source_id } else { &id }),
|
|
0,
|
|
cx,
|
|
);
|
|
w.write_str("</h4></section>");
|
|
}
|
|
clean::AssocTypeItem(tydef, _bounds) => {
|
|
let source_id = format!("{item_type}.{name}");
|
|
let id = cx.derive_id(&source_id);
|
|
write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
|
|
render_rightside(w, cx, item, render_mode);
|
|
if trait_.is_some() {
|
|
// Anchors are only used on trait impls.
|
|
write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>");
|
|
}
|
|
w.write_str("<h4 class=\"code-header\">");
|
|
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("</h4></section>");
|
|
}
|
|
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("</summary>");
|
|
w.push_buffer(doc_buffer);
|
|
w.push_str("</details>");
|
|
}
|
|
}
|
|
|
|
let mut impl_items = Buffer::empty_from(w);
|
|
let mut default_impl_items = Buffer::empty_from(w);
|
|
let impl_ = i.inner_impl();
|
|
|
|
// Impl items are grouped by kinds:
|
|
//
|
|
// 1. Constants
|
|
// 2. Types
|
|
// 3. Functions
|
|
//
|
|
// This order is because you can have associated constants used in associated types (like array
|
|
// length), and both in associcated functions. So with this order, when reading from top to
|
|
// bottom, you should see items definitions before they're actually used most of the time.
|
|
let mut assoc_types = Vec::new();
|
|
let mut methods = Vec::new();
|
|
|
|
if !impl_.is_negative_trait_impl() {
|
|
for trait_item in &impl_.items {
|
|
match trait_item.kind {
|
|
clean::MethodItem(..) | clean::TyMethodItem(_) => methods.push(trait_item),
|
|
clean::TyAssocTypeItem(..) | clean::AssocTypeItem(..) => {
|
|
assoc_types.push(trait_item)
|
|
}
|
|
clean::TyAssocConstItem(..) | clean::AssocConstItem(_) => {
|
|
// We render it directly since they're supposed to come first.
|
|
doc_impl_item(
|
|
&mut default_impl_items,
|
|
&mut impl_items,
|
|
cx,
|
|
trait_item,
|
|
if trait_.is_some() { &i.impl_item } else { parent },
|
|
link,
|
|
render_mode,
|
|
false,
|
|
trait_,
|
|
rendering_params,
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
for assoc_type in assoc_types {
|
|
doc_impl_item(
|
|
&mut default_impl_items,
|
|
&mut impl_items,
|
|
cx,
|
|
assoc_type,
|
|
if trait_.is_some() { &i.impl_item } else { parent },
|
|
link,
|
|
render_mode,
|
|
false,
|
|
trait_,
|
|
rendering_params,
|
|
);
|
|
}
|
|
for method in methods {
|
|
doc_impl_item(
|
|
&mut default_impl_items,
|
|
&mut impl_items,
|
|
cx,
|
|
method,
|
|
if trait_.is_some() { &i.impl_item } else { 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,
|
|
render_mode: RenderMode,
|
|
rendering_params: ImplRenderingParameters,
|
|
) {
|
|
for trait_item in &t.items {
|
|
// Skip over any default trait items that are impossible to reference
|
|
// (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_associated_item((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,
|
|
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_
|
|
&& !impl_.is_negative_trait_impl()
|
|
{
|
|
render_default_items(
|
|
&mut default_impl_items,
|
|
&mut impl_items,
|
|
cx,
|
|
t,
|
|
impl_,
|
|
&i.impl_item,
|
|
render_mode,
|
|
rendering_params,
|
|
);
|
|
}
|
|
}
|
|
if render_mode == RenderMode::Normal {
|
|
let toggled = !(impl_items.is_empty() && default_impl_items.is_empty());
|
|
if toggled {
|
|
close_tags.push("</details>");
|
|
write!(
|
|
w,
|
|
"<details class=\"toggle implementors-toggle\"{}>\
|
|
<summary>",
|
|
if rendering_params.toggle_open_by_default { " open" } else { "" }
|
|
);
|
|
}
|
|
render_impl_summary(
|
|
w,
|
|
cx,
|
|
i,
|
|
parent,
|
|
rendering_params.show_def_docs,
|
|
use_absolute,
|
|
aliases,
|
|
);
|
|
if toggled {
|
|
w.write_str("</summary>");
|
|
}
|
|
|
|
if let Some(ref dox) = i.impl_item.opt_doc_value() {
|
|
if trait_.is_none() && impl_.items.is_empty() {
|
|
w.write_str(
|
|
"<div class=\"item-info\">\
|
|
<div class=\"stab empty-impl\">This impl block contains no items.</div>\
|
|
</div>",
|
|
);
|
|
}
|
|
write!(
|
|
w,
|
|
"<div class=\"docblock\">{}</div>",
|
|
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("<div class=\"impl-items\">");
|
|
close_tags.push("</div>");
|
|
}
|
|
}
|
|
if !default_impl_items.is_empty() || !impl_items.is_empty() {
|
|
w.push_buffer(default_impl_items);
|
|
w.push_buffer(impl_items);
|
|
}
|
|
for tag in close_tags.into_iter().rev() {
|
|
w.write_str(tag);
|
|
}
|
|
}
|
|
|
|
// 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, 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 = match render_mode {
|
|
RenderMode::Normal => item.const_stability(tcx),
|
|
RenderMode::ForDeref { .. } => 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,
|
|
if has_src_ref { "" } else { " rightside" },
|
|
);
|
|
if let Some(link) = src_href {
|
|
if has_stability {
|
|
write!(rightside, " · <a class=\"src\" href=\"{link}\">Source</a>")
|
|
} else {
|
|
write!(rightside, "<a class=\"src rightside\" href=\"{link}\">Source</a>")
|
|
}
|
|
}
|
|
if has_stability && has_src_ref {
|
|
write!(w, "<span class=\"rightside\">{}</span>", 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,
|
|
show_def_docs: bool,
|
|
use_absolute: Option<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 inner_impl = i.inner_impl();
|
|
let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id));
|
|
let aliases = if aliases.is_empty() {
|
|
String::new()
|
|
} else {
|
|
format!(" data-aliases=\"{}\"", aliases.join(","))
|
|
};
|
|
write!(w, "<section id=\"{id}\" class=\"impl\"{aliases}>");
|
|
render_rightside(w, cx, &i.impl_item, RenderMode::Normal);
|
|
write!(
|
|
w,
|
|
"<a href=\"#{id}\" class=\"anchor\">§</a>\
|
|
<h3 class=\"code-header\">"
|
|
);
|
|
|
|
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("<div class=\"where\"> ");
|
|
assoc_type(
|
|
w,
|
|
it,
|
|
&tydef.generics,
|
|
&[], // intentionally leaving out bounds
|
|
Some(&tydef.type_),
|
|
AssocItemLink::Anchor(None),
|
|
0,
|
|
cx,
|
|
);
|
|
w.write_str(";</div>");
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write!(w, "{}", inner_impl.print(false, cx));
|
|
}
|
|
w.write_str("</h3>");
|
|
|
|
let is_trait = inner_impl.trait_.is_some();
|
|
if is_trait && let Some(portability) = portability(&i.impl_item, Some(parent)) {
|
|
write!(
|
|
w,
|
|
"<span class=\"item-info\">\
|
|
<div class=\"stab portability\">{portability}</div>\
|
|
</span>",
|
|
);
|
|
}
|
|
|
|
w.write_str("</section>");
|
|
}
|
|
|
|
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
|
|
// and https://url.spec.whatwg.org/#urlencoded-parsing
|
|
// and https://url.spec.whatwg.org/#url-code-points
|
|
fn dont_escape(c: u8) -> bool {
|
|
(b'a' <= c && c <= b'z')
|
|
|| (b'A' <= c && c <= b'Z')
|
|
|| (b'0' <= c && c <= b'9')
|
|
|| c == b'-'
|
|
|| c == b'_'
|
|
|| c == b'.'
|
|
|| c == b','
|
|
|| c == b'~'
|
|
|| c == b'!'
|
|
|| c == b'\''
|
|
|| c == b'('
|
|
|| c == b')'
|
|
|| c == b'*'
|
|
|| c == b'/'
|
|
|| c == b';'
|
|
|| c == b':'
|
|
|| c == b'?'
|
|
// As described in urlencoded-parsing, the
|
|
// first `=` is the one that separates key from
|
|
// value. Following `=`s are part of the value.
|
|
|| c == b'='
|
|
}
|
|
let mut st = String::new();
|
|
let mut last_match = 0;
|
|
for (idx, b) in s.bytes().enumerate() {
|
|
if dont_escape(b) {
|
|
continue;
|
|
}
|
|
|
|
if last_match != idx {
|
|
// Invariant: `idx` must be the first byte in a character at this point.
|
|
st += &s[last_match..idx];
|
|
}
|
|
if b == b' ' {
|
|
// URL queries are decoded with + replaced with SP.
|
|
// While the same is not true for hashes, rustdoc only needs to be
|
|
// consistent with itself when encoding them.
|
|
st += "+";
|
|
} else {
|
|
write!(st, "%{b:02X}").unwrap();
|
|
}
|
|
// Invariant: if the current byte is not at the start of a multi-byte character,
|
|
// we need to get down here so that when the next turn of the loop comes around,
|
|
// last_match winds up equalling idx.
|
|
//
|
|
// In other words, dont_escape must always return `false` in multi-byte character.
|
|
last_match = idx + 1;
|
|
}
|
|
|
|
if last_match != 0 {
|
|
st += &s[last_match..];
|
|
st
|
|
} else {
|
|
s
|
|
}
|
|
}
|
|
|
|
fn get_id_for_impl<'tcx>(tcx: TyCtxt<'tcx>, impl_id: ItemId) -> String {
|
|
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
|
let (type_, trait_) = match impl_id {
|
|
ItemId::Auto { trait_, for_ } => {
|
|
let ty = tcx.type_of(for_).skip_binder();
|
|
(ty, Some(ty::TraitRef::new(tcx, trait_, [ty])))
|
|
}
|
|
ItemId::Blanket { impl_id, .. } | ItemId::DefId(impl_id) => {
|
|
match tcx.impl_subject(impl_id).skip_binder() {
|
|
ty::ImplSubject::Trait(trait_ref) => {
|
|
(trait_ref.args[0].expect_ty(), Some(trait_ref))
|
|
}
|
|
ty::ImplSubject::Inherent(ty) => (ty, None),
|
|
}
|
|
}
|
|
};
|
|
with_forced_trimmed_paths!(small_url_encode(if let Some(trait_) = trait_ {
|
|
format!("impl-{trait_}-for-{type_}", trait_ = trait_.print_only_trait_path())
|
|
} else {
|
|
format!("impl-{type_}")
|
|
}))
|
|
}
|
|
|
|
fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
|
|
match item.kind {
|
|
clean::ItemKind::ImplItem(ref i) if i.trait_.is_some() => {
|
|
// Alternative format produces no URLs,
|
|
// so this parameter does nothing.
|
|
Some((format!("{:#}", i.for_.print(cx)), get_id_for_impl(cx.tcx(), item.item_id)))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// 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`.
|
|
pub(crate) fn get_filtered_impls_for_reference<'a>(
|
|
shared: &'a Rc<SharedContext<'_>>,
|
|
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)
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
pub(crate) enum ItemSection {
|
|
Reexports,
|
|
PrimitiveTypes,
|
|
Modules,
|
|
Macros,
|
|
Structs,
|
|
Enums,
|
|
Constants,
|
|
Statics,
|
|
Traits,
|
|
Functions,
|
|
TypeAliases,
|
|
Unions,
|
|
Implementations,
|
|
TypeMethods,
|
|
Methods,
|
|
StructFields,
|
|
Variants,
|
|
AssociatedTypes,
|
|
AssociatedConstants,
|
|
ForeignTypes,
|
|
Keywords,
|
|
AttributeMacros,
|
|
DeriveMacros,
|
|
TraitAliases,
|
|
}
|
|
|
|
impl ItemSection {
|
|
const ALL: &'static [Self] = {
|
|
use ItemSection::*;
|
|
// NOTE: The order here affects the order in the UI.
|
|
// Keep this synchronized with addSidebarItems in main.js
|
|
&[
|
|
Reexports,
|
|
PrimitiveTypes,
|
|
Modules,
|
|
Macros,
|
|
Structs,
|
|
Enums,
|
|
Constants,
|
|
Statics,
|
|
Traits,
|
|
Functions,
|
|
TypeAliases,
|
|
Unions,
|
|
Implementations,
|
|
TypeMethods,
|
|
Methods,
|
|
StructFields,
|
|
Variants,
|
|
AssociatedTypes,
|
|
AssociatedConstants,
|
|
ForeignTypes,
|
|
Keywords,
|
|
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::TypeAliases => "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::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::TypeAliases => "Type Aliases",
|
|
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::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::TypeAlias => ItemSection::TypeAliases,
|
|
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::ProcAttribute => ItemSection::AttributeMacros,
|
|
ItemType::ProcDerive => ItemSection::DeriveMacros,
|
|
ItemType::TraitAlias => ItemSection::TraitAliases,
|
|
}
|
|
}
|
|
|
|
/// 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<String> {
|
|
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);
|
|
let fqp = cache.exact_paths.get(&did).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);
|
|
if let Some(trait_) = trait_ {
|
|
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: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &clean::Item) {
|
|
let tcx = cx.tcx();
|
|
let def_id = item.item_id.expect_def_id();
|
|
let key = tcx.def_path_hash(def_id);
|
|
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!(
|
|
&mut w,
|
|
"<div class=\"docblock scraped-example-list\">\
|
|
<span></span>\
|
|
<h5 id=\"{id}\">\
|
|
<a href=\"#{id}\">Examples found in repository</a>\
|
|
<a class=\"scrape-help\" href=\"{root_path}scrape-examples-help.html\">?</a>\
|
|
</h5>",
|
|
root_path = cx.root_path(),
|
|
id = id
|
|
)
|
|
.unwrap();
|
|
|
|
// Create a URL to a particular location in a reverse-dependency's source file
|
|
let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) {
|
|
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!("{}{}#{anchor}", cx.root_path(), call_data.url);
|
|
(url, title)
|
|
};
|
|
|
|
// Generate the HTML for a single example, being the title and code block
|
|
let write_example = |mut w: &mut W, (path, call_data): (&PathBuf, &CallData)| -> bool {
|
|
let contents = match fs::read_to_string(&path) {
|
|
Ok(contents) => contents,
|
|
Err(err) => {
|
|
let span = item.span(tcx).map_or(DUMMY_SP, |span| span.inner());
|
|
tcx.dcx().span_err(span, format!("failed to read file {}: {err}", path.display()));
|
|
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();
|
|
|
|
// 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()?.into_local_path()?;
|
|
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(DUMMY_SP);
|
|
|
|
let mut decoration_info = FxIndexMap::default();
|
|
decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
|
|
decoration_info.insert("highlight", byte_ranges);
|
|
|
|
sources::print_src(
|
|
&mut w,
|
|
contents_subset,
|
|
file_span,
|
|
cx,
|
|
&cx.root_path(),
|
|
highlight::DecorationInfo(decoration_info),
|
|
sources::SourceContext::Embedded(sources::ScrapedInfo {
|
|
needs_expansion,
|
|
offset: line_min,
|
|
name: &call_data.display_name,
|
|
url: init_url,
|
|
title: init_title,
|
|
locations: locations_encoded,
|
|
}),
|
|
);
|
|
|
|
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. We instead proxy relevance with the following heuristics:
|
|
// 1. Code written to be an example is better than code not written to be an example, e.g.
|
|
// a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo
|
|
// directory structure in Rustdoc, so we proxy this by prioritizing code that comes from
|
|
// a --crate-type bin.
|
|
// 2. Smaller examples are better than large examples. So we prioritize snippets that have
|
|
// the smallest number of lines in their enclosing item.
|
|
// 3. Finally we sort by the displayed file name, which is arbitrary but prevents the
|
|
// ordering of examples from randomly changing between Rustdoc invocations.
|
|
let ordered_locations = {
|
|
fn sort_criterion<'a>(
|
|
(_, call_data): &(&PathBuf, &'a CallData),
|
|
) -> (bool, u32, &'a String) {
|
|
// Use the first location because that's what the user will see initially
|
|
let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
|
|
(!call_data.is_bin, hi - lo, &call_data.display_name)
|
|
}
|
|
|
|
let mut locs = call_locations.iter().collect::<Vec<_>>();
|
|
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 W, 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(&mut w, &mut it);
|
|
|
|
// Then add the remaining examples in a hidden section.
|
|
if it.peek().is_some() {
|
|
write!(
|
|
w,
|
|
"<details class=\"toggle more-examples-toggle\">\
|
|
<summary class=\"hideme\">\
|
|
<span>More examples</span>\
|
|
</summary>\
|
|
<div class=\"hide-more\">Hide additional examples</div>\
|
|
<div class=\"more-scraped-examples\">\
|
|
<div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>"
|
|
)
|
|
.unwrap();
|
|
|
|
// Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
|
|
// make the page arbitrarily huge!
|
|
for _ in 0..MAX_FULL_EXAMPLES {
|
|
write_and_skip_failure(&mut w, &mut it);
|
|
}
|
|
|
|
// For the remaining examples, generate a <ul> containing links to the source files.
|
|
if it.peek().is_some() {
|
|
w.write_str(
|
|
r#"<div class="example-links">Additional examples can be found in:<br><ul>"#,
|
|
)
|
|
.unwrap();
|
|
it.for_each(|(_, call_data)| {
|
|
let (url, _) = link_to_loc(call_data, &call_data.locations[0]);
|
|
write!(
|
|
w,
|
|
r#"<li><a href="{url}">{name}</a></li>"#,
|
|
url = url,
|
|
name = call_data.display_name
|
|
)
|
|
.unwrap();
|
|
});
|
|
w.write_str("</ul></div>").unwrap();
|
|
}
|
|
|
|
w.write_str("</div></details>").unwrap();
|
|
}
|
|
|
|
w.write_str("</div>").unwrap();
|
|
}
|