1 //! Rustdoc's HTML rendering module.
2 //!
3 //! This modules contains the bulk of the logic necessary for rendering a
4 //! rustdoc `clean::Crate` instance to a set of static HTML pages. This
5 //! rendering process is largely driven by the `format!` syntax extension to
6 //! perform all I/O into files and streams.
7 //!
8 //! The rendering process is largely driven by the `Context` and `Cache`
9 //! structures. The cache is pre-populated by crawling the crate in question,
10 //! and then it is shared among the various rendering threads. The cache is meant
11 //! to be a fairly large structure not implementing `Clone` (because it's shared
12 //! among threads). The context, however, should be a lightweight structure. This
13 //! is cloned per-thread and contains information about what is currently being
14 //! rendered.
15 //!
16 //! In order to speed up rendering (mostly because of markdown rendering), the
17 //! rendering process has been parallelized. This parallelization is only
18 //! exposed through the `crate` method on the context, and then also from the
19 //! fact that the shared cache is stored in TLS (and must be accessed as such).
20 //!
21 //! In addition to rendering the crate itself, this module is also responsible
22 //! for creating the corresponding search index and source file renderings.
23 //! These threads are not parallelized (they haven't been a bottleneck yet), and
24 //! both occur before the crate is rendered.
25
26 pub(crate) mod search_index;
27
28 #[cfg(test)]
29 mod tests;
30
31 mod context;
32 mod print_item;
33 mod sidebar;
34 mod span_map;
35 mod type_layout;
36 mod write_shared;
37
38 pub(crate) use self::context::*;
39 pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc};
40
41 use std::collections::VecDeque;
42 use std::fmt::{self, Write};
43 use std::fs;
44 use std::iter::Peekable;
45 use std::path::PathBuf;
46 use std::rc::Rc;
47 use std::str;
48 use std::string::ToString;
49
50 use askama::Template;
51 use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
52 use rustc_data_structures::captures::Captures;
53 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
54 use rustc_hir::def_id::{DefId, DefIdSet};
55 use rustc_hir::Mutability;
56 use rustc_middle::middle::stability;
57 use rustc_middle::ty::TyCtxt;
58 use rustc_span::{
59 symbol::{sym, Symbol},
60 BytePos, FileName, RealFileName,
61 };
62 use serde::ser::{SerializeMap, SerializeSeq};
63 use serde::{Serialize, Serializer};
64
65 use crate::clean::{self, ItemId, RenderedLink, SelfTy};
66 use crate::error::Error;
67 use crate::formats::cache::Cache;
68 use crate::formats::item_type::ItemType;
69 use crate::formats::{AssocItemRender, Impl, RenderMode};
70 use crate::html::escape::Escape;
71 use crate::html::format::{
72 display_fn, href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
73 print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space,
74 Buffer, Ending, HrefError, PrintWithSpace,
75 };
76 use crate::html::highlight;
77 use crate::html::markdown::{
78 HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine,
79 };
80 use crate::html::sources;
81 use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
82 use crate::scrape_examples::{CallData, CallLocation};
83 use crate::try_none;
84 use crate::DOC_RUST_LANG_ORG_CHANNEL;
85
ensure_trailing_slash(v: &str) -> impl fmt::Display + '_86 pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ {
87 crate::html::format::display_fn(move |f| {
88 if !v.ends_with('/') && !v.is_empty() { write!(f, "{}/", v) } else { f.write_str(v) }
89 })
90 }
91
92 // Helper structs for rendering items/sidebars and carrying along contextual
93 // information
94
95 /// Struct representing one entry in the JS search index. These are all emitted
96 /// by hand to a large JS file at the end of cache-creation.
97 #[derive(Debug)]
98 pub(crate) struct IndexItem {
99 pub(crate) ty: ItemType,
100 pub(crate) name: Symbol,
101 pub(crate) path: String,
102 pub(crate) desc: String,
103 pub(crate) parent: Option<DefId>,
104 pub(crate) parent_idx: Option<usize>,
105 pub(crate) search_type: Option<IndexItemFunctionType>,
106 pub(crate) aliases: Box<[Symbol]>,
107 pub(crate) deprecation: Option<Deprecation>,
108 }
109
110 /// A type used for the search index.
111 #[derive(Debug)]
112 pub(crate) struct RenderType {
113 id: Option<RenderTypeId>,
114 generics: Option<Vec<RenderType>>,
115 }
116
117 impl Serialize for RenderType {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,118 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119 where
120 S: Serializer,
121 {
122 let id = match &self.id {
123 // 0 is a sentinel, everything else is one-indexed
124 None => 0,
125 Some(RenderTypeId::Index(idx)) => idx + 1,
126 _ => panic!("must convert render types to indexes before serializing"),
127 };
128 if let Some(generics) = &self.generics {
129 let mut seq = serializer.serialize_seq(None)?;
130 seq.serialize_element(&id)?;
131 seq.serialize_element(generics)?;
132 seq.end()
133 } else {
134 id.serialize(serializer)
135 }
136 }
137 }
138
139 #[derive(Clone, Debug)]
140 pub(crate) enum RenderTypeId {
141 DefId(DefId),
142 Primitive(clean::PrimitiveType),
143 Index(usize),
144 }
145
146 /// Full type of functions/methods in the search index.
147 #[derive(Debug)]
148 pub(crate) struct IndexItemFunctionType {
149 inputs: Vec<RenderType>,
150 output: Vec<RenderType>,
151 }
152
153 impl Serialize for IndexItemFunctionType {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,154 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155 where
156 S: Serializer,
157 {
158 // If we couldn't figure out a type, just write `0`.
159 let has_missing = self
160 .inputs
161 .iter()
162 .chain(self.output.iter())
163 .any(|i| i.id.is_none() && i.generics.is_none());
164 if has_missing {
165 0.serialize(serializer)
166 } else {
167 let mut seq = serializer.serialize_seq(None)?;
168 match &self.inputs[..] {
169 [one] if one.generics.is_none() => seq.serialize_element(one)?,
170 _ => seq.serialize_element(&self.inputs)?,
171 }
172 match &self.output[..] {
173 [] => {}
174 [one] if one.generics.is_none() => seq.serialize_element(one)?,
175 _ => seq.serialize_element(&self.output)?,
176 }
177 seq.end()
178 }
179 }
180 }
181
182 #[derive(Debug, Clone)]
183 pub(crate) struct StylePath {
184 /// The path to the theme
185 pub(crate) path: PathBuf,
186 }
187
188 impl StylePath {
basename(&self) -> Result<String, Error>189 pub(crate) fn basename(&self) -> Result<String, Error> {
190 Ok(try_none!(try_none!(self.path.file_stem(), &self.path).to_str(), &self.path).to_string())
191 }
192 }
193
194 #[derive(Debug, Eq, PartialEq, Hash)]
195 struct ItemEntry {
196 url: String,
197 name: String,
198 }
199
200 impl ItemEntry {
new(mut url: String, name: String) -> ItemEntry201 fn new(mut url: String, name: String) -> ItemEntry {
202 while url.starts_with('/') {
203 url.remove(0);
204 }
205 ItemEntry { url, name }
206 }
207 }
208
209 impl ItemEntry {
print(&self) -> impl fmt::Display + '_210 pub(crate) fn print(&self) -> impl fmt::Display + '_ {
211 crate::html::format::display_fn(move |f| {
212 write!(f, "<a href=\"{}\">{}</a>", self.url, Escape(&self.name))
213 })
214 }
215 }
216
217 impl PartialOrd for ItemEntry {
partial_cmp(&self, other: &ItemEntry) -> Option<::std::cmp::Ordering>218 fn partial_cmp(&self, other: &ItemEntry) -> Option<::std::cmp::Ordering> {
219 Some(self.cmp(other))
220 }
221 }
222
223 impl Ord for ItemEntry {
cmp(&self, other: &ItemEntry) -> ::std::cmp::Ordering224 fn cmp(&self, other: &ItemEntry) -> ::std::cmp::Ordering {
225 self.name.cmp(&other.name)
226 }
227 }
228
229 #[derive(Debug)]
230 struct AllTypes {
231 structs: FxHashSet<ItemEntry>,
232 enums: FxHashSet<ItemEntry>,
233 unions: FxHashSet<ItemEntry>,
234 primitives: FxHashSet<ItemEntry>,
235 traits: FxHashSet<ItemEntry>,
236 macros: FxHashSet<ItemEntry>,
237 functions: FxHashSet<ItemEntry>,
238 typedefs: FxHashSet<ItemEntry>,
239 opaque_tys: FxHashSet<ItemEntry>,
240 statics: FxHashSet<ItemEntry>,
241 constants: FxHashSet<ItemEntry>,
242 attribute_macros: FxHashSet<ItemEntry>,
243 derive_macros: FxHashSet<ItemEntry>,
244 trait_aliases: FxHashSet<ItemEntry>,
245 }
246
247 impl AllTypes {
new() -> AllTypes248 fn new() -> AllTypes {
249 let new_set = |cap| FxHashSet::with_capacity_and_hasher(cap, Default::default());
250 AllTypes {
251 structs: new_set(100),
252 enums: new_set(100),
253 unions: new_set(100),
254 primitives: new_set(26),
255 traits: new_set(100),
256 macros: new_set(100),
257 functions: new_set(100),
258 typedefs: new_set(100),
259 opaque_tys: new_set(100),
260 statics: new_set(100),
261 constants: new_set(100),
262 attribute_macros: new_set(100),
263 derive_macros: new_set(100),
264 trait_aliases: new_set(100),
265 }
266 }
267
append(&mut self, item_name: String, item_type: &ItemType)268 fn append(&mut self, item_name: String, item_type: &ItemType) {
269 let mut url: Vec<_> = item_name.split("::").skip(1).collect();
270 if let Some(name) = url.pop() {
271 let new_url = format!("{}/{}.{}.html", url.join("/"), item_type, name);
272 url.push(name);
273 let name = url.join("::");
274 match *item_type {
275 ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)),
276 ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)),
277 ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)),
278 ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)),
279 ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)),
280 ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)),
281 ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)),
282 ItemType::Typedef => self.typedefs.insert(ItemEntry::new(new_url, name)),
283 ItemType::OpaqueTy => self.opaque_tys.insert(ItemEntry::new(new_url, name)),
284 ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)),
285 ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)),
286 ItemType::ProcAttribute => {
287 self.attribute_macros.insert(ItemEntry::new(new_url, name))
288 }
289 ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)),
290 ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)),
291 _ => true,
292 };
293 }
294 }
295
item_sections(&self) -> FxHashSet<ItemSection>296 fn item_sections(&self) -> FxHashSet<ItemSection> {
297 let mut sections = FxHashSet::default();
298
299 if !self.structs.is_empty() {
300 sections.insert(ItemSection::Structs);
301 }
302 if !self.enums.is_empty() {
303 sections.insert(ItemSection::Enums);
304 }
305 if !self.unions.is_empty() {
306 sections.insert(ItemSection::Unions);
307 }
308 if !self.primitives.is_empty() {
309 sections.insert(ItemSection::PrimitiveTypes);
310 }
311 if !self.traits.is_empty() {
312 sections.insert(ItemSection::Traits);
313 }
314 if !self.macros.is_empty() {
315 sections.insert(ItemSection::Macros);
316 }
317 if !self.functions.is_empty() {
318 sections.insert(ItemSection::Functions);
319 }
320 if !self.typedefs.is_empty() {
321 sections.insert(ItemSection::TypeDefinitions);
322 }
323 if !self.opaque_tys.is_empty() {
324 sections.insert(ItemSection::OpaqueTypes);
325 }
326 if !self.statics.is_empty() {
327 sections.insert(ItemSection::Statics);
328 }
329 if !self.constants.is_empty() {
330 sections.insert(ItemSection::Constants);
331 }
332 if !self.attribute_macros.is_empty() {
333 sections.insert(ItemSection::AttributeMacros);
334 }
335 if !self.derive_macros.is_empty() {
336 sections.insert(ItemSection::DeriveMacros);
337 }
338 if !self.trait_aliases.is_empty() {
339 sections.insert(ItemSection::TraitAliases);
340 }
341
342 sections
343 }
344
print(self, f: &mut Buffer)345 fn print(self, f: &mut Buffer) {
346 fn print_entries(f: &mut Buffer, e: &FxHashSet<ItemEntry>, kind: ItemSection) {
347 if !e.is_empty() {
348 let mut e: Vec<&ItemEntry> = e.iter().collect();
349 e.sort();
350 write!(
351 f,
352 "<h3 id=\"{id}\">{title}</h3><ul class=\"all-items\">",
353 id = kind.id(),
354 title = kind.name(),
355 );
356
357 for s in e.iter() {
358 write!(f, "<li>{}</li>", s.print());
359 }
360
361 f.write_str("</ul>");
362 }
363 }
364
365 f.write_str("<h1>List of all items</h1>");
366 // Note: print_entries does not escape the title, because we know the current set of titles
367 // doesn't require escaping.
368 print_entries(f, &self.structs, ItemSection::Structs);
369 print_entries(f, &self.enums, ItemSection::Enums);
370 print_entries(f, &self.unions, ItemSection::Unions);
371 print_entries(f, &self.primitives, ItemSection::PrimitiveTypes);
372 print_entries(f, &self.traits, ItemSection::Traits);
373 print_entries(f, &self.macros, ItemSection::Macros);
374 print_entries(f, &self.attribute_macros, ItemSection::AttributeMacros);
375 print_entries(f, &self.derive_macros, ItemSection::DeriveMacros);
376 print_entries(f, &self.functions, ItemSection::Functions);
377 print_entries(f, &self.typedefs, ItemSection::TypeDefinitions);
378 print_entries(f, &self.trait_aliases, ItemSection::TraitAliases);
379 print_entries(f, &self.opaque_tys, ItemSection::OpaqueTypes);
380 print_entries(f, &self.statics, ItemSection::Statics);
381 print_entries(f, &self.constants, ItemSection::Constants);
382 }
383 }
384
scrape_examples_help(shared: &SharedContext<'_>) -> String385 fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
386 let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned();
387 content.push_str(&format!(
388 "## More information\n\n\
389 If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).",
390 DOC_RUST_LANG_ORG_CHANNEL));
391
392 let mut ids = IdMap::default();
393 format!(
394 "<div class=\"main-heading\">\
395 <h1>About scraped examples</h1>\
396 </div>\
397 <div>{}</div>",
398 Markdown {
399 content: &content,
400 links: &[],
401 ids: &mut ids,
402 error_codes: shared.codes,
403 edition: shared.edition(),
404 playground: &shared.playground,
405 heading_offset: HeadingOffset::H1
406 }
407 .into_string()
408 )
409 }
410
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>411 fn document<'a, 'cx: 'a>(
412 cx: &'a mut Context<'cx>,
413 item: &'a clean::Item,
414 parent: Option<&'a clean::Item>,
415 heading_offset: HeadingOffset,
416 ) -> impl fmt::Display + 'a + Captures<'cx> {
417 if let Some(ref name) = item.name {
418 info!("Documenting {}", name);
419 }
420
421 display_fn(move |f| {
422 document_item_info(cx, item, parent).render_into(f).unwrap();
423 if parent.is_none() {
424 write!(f, "{}", document_full_collapsible(item, cx, heading_offset))
425 } else {
426 write!(f, "{}", document_full(item, cx, heading_offset))
427 }
428 })
429 }
430
431 /// Render md_text as markdown.
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>432 fn render_markdown<'a, 'cx: 'a>(
433 cx: &'a mut Context<'cx>,
434 md_text: &'a str,
435 links: Vec<RenderedLink>,
436 heading_offset: HeadingOffset,
437 ) -> impl fmt::Display + 'a + Captures<'cx> {
438 display_fn(move |f| {
439 write!(
440 f,
441 "<div class=\"docblock\">{}</div>",
442 Markdown {
443 content: md_text,
444 links: &links,
445 ids: &mut cx.id_map,
446 error_codes: cx.shared.codes,
447 edition: cx.shared.edition(),
448 playground: &cx.shared.playground,
449 heading_offset,
450 }
451 .into_string()
452 )
453 })
454 }
455
456 /// Writes a documentation block containing only the first paragraph of the documentation. If the
457 /// docs are longer, a "Read more" link is appended to the end.
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>458 fn document_short<'a, 'cx: 'a>(
459 item: &'a clean::Item,
460 cx: &'a mut Context<'cx>,
461 link: AssocItemLink<'a>,
462 parent: &'a clean::Item,
463 show_def_docs: bool,
464 ) -> impl fmt::Display + 'a + Captures<'cx> {
465 display_fn(move |f| {
466 document_item_info(cx, item, Some(parent)).render_into(f).unwrap();
467 if !show_def_docs {
468 return Ok(());
469 }
470 let s = item.doc_value();
471 if !s.is_empty() {
472 let (mut summary_html, has_more_content) =
473 MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
474
475 if has_more_content {
476 let link = format!(r#" <a{}>Read more</a>"#, assoc_href_attr(item, link, cx));
477
478 if let Some(idx) = summary_html.rfind("</p>") {
479 summary_html.insert_str(idx, &link);
480 } else {
481 summary_html.push_str(&link);
482 }
483 }
484
485 write!(f, "<div class='docblock'>{}</div>", summary_html)?;
486 }
487 Ok(())
488 })
489 }
490
document_full_collapsible<'a, 'cx: 'a>( item: &'a clean::Item, cx: &'a mut Context<'cx>, heading_offset: HeadingOffset, ) -> impl fmt::Display + 'a + Captures<'cx>491 fn document_full_collapsible<'a, 'cx: 'a>(
492 item: &'a clean::Item,
493 cx: &'a mut Context<'cx>,
494 heading_offset: HeadingOffset,
495 ) -> impl fmt::Display + 'a + Captures<'cx> {
496 document_full_inner(item, cx, true, heading_offset)
497 }
498
document_full<'a, 'cx: 'a>( item: &'a clean::Item, cx: &'a mut Context<'cx>, heading_offset: HeadingOffset, ) -> impl fmt::Display + 'a + Captures<'cx>499 fn document_full<'a, 'cx: 'a>(
500 item: &'a clean::Item,
501 cx: &'a mut Context<'cx>,
502 heading_offset: HeadingOffset,
503 ) -> impl fmt::Display + 'a + Captures<'cx> {
504 document_full_inner(item, cx, false, heading_offset)
505 }
506
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>507 fn document_full_inner<'a, 'cx: 'a>(
508 item: &'a clean::Item,
509 cx: &'a mut Context<'cx>,
510 is_collapsible: bool,
511 heading_offset: HeadingOffset,
512 ) -> impl fmt::Display + 'a + Captures<'cx> {
513 display_fn(move |f| {
514 if let Some(s) = item.opt_doc_value() {
515 debug!("Doc block: =====\n{}\n=====", s);
516 if is_collapsible {
517 write!(
518 f,
519 "<details class=\"toggle top-doc\" open>\
520 <summary class=\"hideme\">\
521 <span>Expand description</span>\
522 </summary>{}</details>",
523 render_markdown(cx, &s, item.links(cx), heading_offset)
524 )?;
525 } else {
526 write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?;
527 }
528 }
529
530 let kind = match &*item.kind {
531 clean::ItemKind::StrippedItem(box kind) | kind => kind,
532 };
533
534 if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
535 render_call_locations(f, cx, item);
536 }
537 Ok(())
538 })
539 }
540
541 #[derive(Template)]
542 #[template(path = "item_info.html")]
543 struct ItemInfo {
544 items: Vec<ShortItemInfo>,
545 }
546 /// Add extra information about an item such as:
547 ///
548 /// * Stability
549 /// * Deprecated
550 /// * Required features (through the `doc_cfg` feature)
document_item_info( cx: &mut Context<'_>, item: &clean::Item, parent: Option<&clean::Item>, ) -> ItemInfo551 fn document_item_info(
552 cx: &mut Context<'_>,
553 item: &clean::Item,
554 parent: Option<&clean::Item>,
555 ) -> ItemInfo {
556 let items = short_item_info(item, cx, parent);
557 ItemInfo { items }
558 }
559
portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String>560 fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> {
561 let cfg = match (&item.cfg, parent.and_then(|p| p.cfg.as_ref())) {
562 (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
563 (cfg, _) => cfg.as_deref().cloned(),
564 };
565
566 debug!(
567 "Portability {:?} {:?} (parent: {:?}) - {:?} = {:?}",
568 item.name,
569 item.cfg,
570 parent,
571 parent.and_then(|p| p.cfg.as_ref()),
572 cfg
573 );
574
575 Some(cfg?.render_long_html())
576 }
577
578 #[derive(Template)]
579 #[template(path = "short_item_info.html")]
580 enum ShortItemInfo {
581 /// A message describing the deprecation of this item
582 Deprecation {
583 message: String,
584 },
585 /// The feature corresponding to an unstable item, and optionally
586 /// a tracking issue URL and number.
587 Unstable {
588 feature: String,
589 tracking: Option<(String, u32)>,
590 },
591 Portability {
592 message: String,
593 },
594 }
595
596 /// Render the stability, deprecation and portability information that is displayed at the top of
597 /// the item's documentation.
short_item_info( item: &clean::Item, cx: &mut Context<'_>, parent: Option<&clean::Item>, ) -> Vec<ShortItemInfo>598 fn short_item_info(
599 item: &clean::Item,
600 cx: &mut Context<'_>,
601 parent: Option<&clean::Item>,
602 ) -> Vec<ShortItemInfo> {
603 let mut extra_info = vec![];
604
605 if let Some(depr @ Deprecation { note, since, is_since_rustc_version: _, suggestion: _ }) =
606 item.deprecation(cx.tcx())
607 {
608 // We display deprecation messages for #[deprecated], but only display
609 // the future-deprecation messages for rustc versions.
610 let mut message = if let Some(since) = since {
611 let since = since.as_str();
612 if !stability::deprecation_in_effect(&depr) {
613 if since == "TBD" {
614 String::from("Deprecating in a future Rust version")
615 } else {
616 format!("Deprecating in {}", Escape(since))
617 }
618 } else {
619 format!("Deprecated since {}", Escape(since))
620 }
621 } else {
622 String::from("Deprecated")
623 };
624
625 if let Some(note) = note {
626 let note = note.as_str();
627 let html = MarkdownItemInfo(note, &mut cx.id_map);
628 message.push_str(": ");
629 message.push_str(&html.into_string());
630 }
631 extra_info.push(ShortItemInfo::Deprecation { message });
632 }
633
634 // Render unstable items. But don't render "rustc_private" crates (internal compiler crates).
635 // Those crates are permanently unstable so it makes no sense to render "unstable" everywhere.
636 if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item
637 .stability(cx.tcx())
638 .as_ref()
639 .filter(|stab| stab.feature != sym::rustc_private)
640 .map(|stab| (stab.level, stab.feature))
641 {
642 let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue)
643 {
644 Some((url.clone(), issue.get()))
645 } else {
646 None
647 };
648 extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking });
649 }
650
651 if let Some(message) = portability(item, parent) {
652 extra_info.push(ShortItemInfo::Portability { message });
653 }
654
655 extra_info
656 }
657
658 // Render the list of items inside one of the sections "Trait Implementations",
659 // "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages).
render_impls( cx: &mut Context<'_>, mut w: impl Write, impls: &[&Impl], containing_item: &clean::Item, toggle_open_by_default: bool, )660 pub(crate) fn render_impls(
661 cx: &mut Context<'_>,
662 mut w: impl Write,
663 impls: &[&Impl],
664 containing_item: &clean::Item,
665 toggle_open_by_default: bool,
666 ) {
667 let tcx = cx.tcx();
668 let mut rendered_impls = impls
669 .iter()
670 .map(|i| {
671 let did = i.trait_did().unwrap();
672 let provided_trait_methods = i.inner_impl().provided_trait_methods(tcx);
673 let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods);
674 let mut buffer = Buffer::new();
675 render_impl(
676 &mut buffer,
677 cx,
678 i,
679 containing_item,
680 assoc_link,
681 RenderMode::Normal,
682 None,
683 &[],
684 ImplRenderingParameters {
685 show_def_docs: true,
686 show_default_items: true,
687 show_non_assoc_items: true,
688 toggle_open_by_default,
689 },
690 );
691 buffer.into_inner()
692 })
693 .collect::<Vec<_>>();
694 rendered_impls.sort();
695 w.write_str(&rendered_impls.join("")).unwrap();
696 }
697
698 /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String699 fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
700 let name = it.name.unwrap();
701 let item_type = it.type_();
702
703 let href = match link {
704 AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
705 AssocItemLink::Anchor(None) => Some(format!("#{}.{}", item_type, name)),
706 AssocItemLink::GotoSource(did, provided_methods) => {
707 // We're creating a link from the implementation of an associated item to its
708 // declaration in the trait declaration.
709 let item_type = match item_type {
710 // For historical but not technical reasons, the item type of methods in
711 // trait declarations depends on whether the method is required (`TyMethod`) or
712 // provided (`Method`).
713 ItemType::Method | ItemType::TyMethod => {
714 if provided_methods.contains(&name) {
715 ItemType::Method
716 } else {
717 ItemType::TyMethod
718 }
719 }
720 // For associated types and constants, no such distinction exists.
721 item_type => item_type,
722 };
723
724 match href(did.expect_def_id(), cx) {
725 Ok((url, ..)) => Some(format!("{}#{}.{}", url, item_type, name)),
726 // The link is broken since it points to an external crate that wasn't documented.
727 // Do not create any link in such case. This is better than falling back to a
728 // dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
729 // (that used to happen in older versions). Indeed, in most cases this dummy would
730 // coincide with the `id`. However, it would not always do so.
731 // In general, this dummy would be incorrect:
732 // If the type with the trait impl also had an inherent impl with an assoc. item of
733 // the *same* name as this impl item, the dummy would link to that one even though
734 // those two items are distinct!
735 // In this scenario, the actual `id` of this impl item would be
736 // `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
737 Err(HrefError::DocumentationNotBuilt) => None,
738 Err(_) => Some(format!("#{}.{}", item_type, name)),
739 }
740 }
741 };
742
743 // If there is no `href` for the reason explained above, simply do not render it which is valid:
744 // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
745 href.map(|href| format!(" href=\"{}\"", href)).unwrap_or_default()
746 }
747
assoc_const( w: &mut Buffer, it: &clean::Item, ty: &clean::Type, default: Option<&clean::ConstantKind>, link: AssocItemLink<'_>, extra: &str, cx: &Context<'_>, )748 fn assoc_const(
749 w: &mut Buffer,
750 it: &clean::Item,
751 ty: &clean::Type,
752 default: Option<&clean::ConstantKind>,
753 link: AssocItemLink<'_>,
754 extra: &str,
755 cx: &Context<'_>,
756 ) {
757 let tcx = cx.tcx();
758 write!(
759 w,
760 "{extra}{vis}const <a{href} class=\"constant\">{name}</a>: {ty}",
761 extra = extra,
762 vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
763 href = assoc_href_attr(it, link, cx),
764 name = it.name.as_ref().unwrap(),
765 ty = ty.print(cx),
766 );
767 if let Some(default) = default {
768 write!(w, " = ");
769
770 // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the
771 // hood which adds noisy underscores and a type suffix to number literals.
772 // This hurts readability in this context especially when more complex expressions
773 // are involved and it doesn't add much of value.
774 // Find a way to print constants here without all that jazz.
775 write!(w, "{}", Escape(&default.value(tcx).unwrap_or_else(|| default.expr(tcx))));
776 }
777 }
778
assoc_type( w: &mut Buffer, it: &clean::Item, generics: &clean::Generics, bounds: &[clean::GenericBound], default: Option<&clean::Type>, link: AssocItemLink<'_>, indent: usize, cx: &Context<'_>, )779 fn assoc_type(
780 w: &mut Buffer,
781 it: &clean::Item,
782 generics: &clean::Generics,
783 bounds: &[clean::GenericBound],
784 default: Option<&clean::Type>,
785 link: AssocItemLink<'_>,
786 indent: usize,
787 cx: &Context<'_>,
788 ) {
789 let tcx = cx.tcx();
790 write!(
791 w,
792 "{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
793 indent = " ".repeat(indent),
794 vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
795 href = assoc_href_attr(it, link, cx),
796 name = it.name.as_ref().unwrap(),
797 generics = generics.print(cx),
798 );
799 if !bounds.is_empty() {
800 write!(w, ": {}", print_generic_bounds(bounds, cx))
801 }
802 // Render the default before the where-clause which aligns with the new recommended style. See #89122.
803 if let Some(default) = default {
804 write!(w, " = {}", default.print(cx))
805 }
806 write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
807 }
808
assoc_method( w: &mut Buffer, meth: &clean::Item, g: &clean::Generics, d: &clean::FnDecl, link: AssocItemLink<'_>, parent: ItemType, cx: &mut Context<'_>, render_mode: RenderMode, )809 fn assoc_method(
810 w: &mut Buffer,
811 meth: &clean::Item,
812 g: &clean::Generics,
813 d: &clean::FnDecl,
814 link: AssocItemLink<'_>,
815 parent: ItemType,
816 cx: &mut Context<'_>,
817 render_mode: RenderMode,
818 ) {
819 let tcx = cx.tcx();
820 let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item");
821 let name = meth.name.as_ref().unwrap();
822 let vis = visibility_print_with_space(meth.visibility(tcx), meth.item_id, cx).to_string();
823 // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
824 // this condition.
825 let constness = match render_mode {
826 RenderMode::Normal => {
827 print_constness_with_space(&header.constness, meth.const_stability(tcx))
828 }
829 RenderMode::ForDeref { .. } => "",
830 };
831 let asyncness = header.asyncness.print_with_space();
832 let unsafety = header.unsafety.print_with_space();
833 let defaultness = print_default_space(meth.is_default());
834 let abi = print_abi_with_space(header.abi).to_string();
835 let href = assoc_href_attr(meth, link, cx);
836
837 // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
838 let generics_len = format!("{:#}", g.print(cx)).len();
839 let mut header_len = "fn ".len()
840 + vis.len()
841 + constness.len()
842 + asyncness.len()
843 + unsafety.len()
844 + defaultness.len()
845 + abi.len()
846 + name.as_str().len()
847 + generics_len;
848
849 let notable_traits = notable_traits_button(&d.output, cx);
850
851 let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
852 header_len += 4;
853 let indent_str = " ";
854 write!(w, "{}", render_attributes_in_pre(meth, indent_str, tcx));
855 (4, indent_str, Ending::NoNewline)
856 } else {
857 render_attributes_in_code(w, meth, tcx);
858 (0, "", Ending::Newline)
859 };
860 w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
861 write!(
862 w,
863 "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn \
864 <a{href} class=\"fn\">{name}</a>{generics}{decl}{notable_traits}{where_clause}",
865 indent = indent_str,
866 vis = vis,
867 constness = constness,
868 asyncness = asyncness,
869 unsafety = unsafety,
870 defaultness = defaultness,
871 abi = abi,
872 href = href,
873 name = name,
874 generics = g.print(cx),
875 decl = d.full_print(header_len, indent, cx),
876 notable_traits = notable_traits.unwrap_or_default(),
877 where_clause = print_where_clause(g, cx, indent, end_newline),
878 );
879 }
880
881 /// Writes a span containing the versions at which an item became stable and/or const-stable. For
882 /// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would
883 /// write a span containing "1.0.0 (const: 1.45.0)".
884 ///
885 /// Returns `true` if a stability annotation was rendered.
886 ///
887 /// Stability and const-stability are considered separately. If the item is unstable, no version
888 /// will be written. If the item is const-unstable, "const: unstable" will be appended to the
889 /// span, with a link to the tracking issue if present. If an item's stability or const-stability
890 /// version matches the version of its enclosing item, that version will be omitted.
891 ///
892 /// Note that it is possible for an unstable function to be const-stable. In that case, the span
893 /// will include the const-stable version, but no stable version will be emitted, as a natural
894 /// consequence of the above rules.
render_stability_since_raw_with_extra( w: &mut Buffer, ver: Option<Symbol>, const_stability: Option<ConstStability>, containing_ver: Option<Symbol>, containing_const_ver: Option<Symbol>, extra_class: &str, ) -> bool895 fn render_stability_since_raw_with_extra(
896 w: &mut Buffer,
897 ver: Option<Symbol>,
898 const_stability: Option<ConstStability>,
899 containing_ver: Option<Symbol>,
900 containing_const_ver: Option<Symbol>,
901 extra_class: &str,
902 ) -> bool {
903 let stable_version = ver.filter(|inner| !inner.is_empty() && Some(*inner) != containing_ver);
904
905 let mut title = String::new();
906 let mut stability = String::new();
907
908 if let Some(ver) = stable_version {
909 stability.push_str(ver.as_str());
910 title.push_str(&format!("Stable since Rust version {}", ver));
911 }
912
913 let const_title_and_stability = match const_stability {
914 Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. })
915 if Some(since) != containing_const_ver =>
916 {
917 Some((format!("const since {}", since), format!("const: {}", since)))
918 }
919 Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => {
920 let unstable = if let Some(n) = issue {
921 format!(
922 r#"<a href="https://github.com/rust-lang/rust/issues/{}" title="Tracking issue for {}">unstable</a>"#,
923 n, feature
924 )
925 } else {
926 String::from("unstable")
927 };
928
929 Some((String::from("const unstable"), format!("const: {}", unstable)))
930 }
931 _ => None,
932 };
933
934 if let Some((const_title, const_stability)) = const_title_and_stability {
935 if !title.is_empty() {
936 title.push_str(&format!(", {}", const_title));
937 } else {
938 title.push_str(&const_title);
939 }
940
941 if !stability.is_empty() {
942 stability.push_str(&format!(" ({})", const_stability));
943 } else {
944 stability.push_str(&const_stability);
945 }
946 }
947
948 if !stability.is_empty() {
949 write!(w, r#"<span class="since{extra_class}" title="{title}">{stability}</span>"#);
950 }
951
952 !stability.is_empty()
953 }
954
955 #[inline]
render_stability_since_raw( w: &mut Buffer, ver: Option<Symbol>, const_stability: Option<ConstStability>, containing_ver: Option<Symbol>, containing_const_ver: Option<Symbol>, ) -> bool956 fn render_stability_since_raw(
957 w: &mut Buffer,
958 ver: Option<Symbol>,
959 const_stability: Option<ConstStability>,
960 containing_ver: Option<Symbol>,
961 containing_const_ver: Option<Symbol>,
962 ) -> bool {
963 render_stability_since_raw_with_extra(
964 w,
965 ver,
966 const_stability,
967 containing_ver,
968 containing_const_ver,
969 "",
970 )
971 }
972
render_assoc_item( w: &mut Buffer, item: &clean::Item, link: AssocItemLink<'_>, parent: ItemType, cx: &mut Context<'_>, render_mode: RenderMode, )973 fn render_assoc_item(
974 w: &mut Buffer,
975 item: &clean::Item,
976 link: AssocItemLink<'_>,
977 parent: ItemType,
978 cx: &mut Context<'_>,
979 render_mode: RenderMode,
980 ) {
981 match &*item.kind {
982 clean::StrippedItem(..) => {}
983 clean::TyMethodItem(m) => {
984 assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
985 }
986 clean::MethodItem(m, _) => {
987 assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
988 }
989 kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => assoc_const(
990 w,
991 item,
992 ty,
993 match kind {
994 clean::TyAssocConstItem(_) => None,
995 clean::AssocConstItem(_, default) => Some(default),
996 _ => unreachable!(),
997 },
998 link,
999 if parent == ItemType::Trait { " " } else { "" },
1000 cx,
1001 ),
1002 clean::TyAssocTypeItem(ref generics, ref bounds) => assoc_type(
1003 w,
1004 item,
1005 generics,
1006 bounds,
1007 None,
1008 link,
1009 if parent == ItemType::Trait { 4 } else { 0 },
1010 cx,
1011 ),
1012 clean::AssocTypeItem(ref ty, ref bounds) => assoc_type(
1013 w,
1014 item,
1015 &ty.generics,
1016 bounds,
1017 Some(ty.item_type.as_ref().unwrap_or(&ty.type_)),
1018 link,
1019 if parent == ItemType::Trait { 4 } else { 0 },
1020 cx,
1021 ),
1022 _ => panic!("render_assoc_item called on non-associated-item"),
1023 }
1024 }
1025
1026 // When an attribute is rendered inside a `<pre>` tag, it is formatted using
1027 // a whitespace prefix and newline.
render_attributes_in_pre<'a, 'b: 'a>( it: &'a clean::Item, prefix: &'a str, tcx: TyCtxt<'b>, ) -> impl fmt::Display + Captures<'a> + Captures<'b>1028 fn render_attributes_in_pre<'a, 'b: 'a>(
1029 it: &'a clean::Item,
1030 prefix: &'a str,
1031 tcx: TyCtxt<'b>,
1032 ) -> impl fmt::Display + Captures<'a> + Captures<'b> {
1033 crate::html::format::display_fn(move |f| {
1034 for a in it.attributes(tcx, false) {
1035 writeln!(f, "{}{}", prefix, a)?;
1036 }
1037 Ok(())
1038 })
1039 }
1040
1041 // When an attribute is rendered inside a <code> tag, it is formatted using
1042 // a div to produce a newline after it.
render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, tcx: TyCtxt<'_>)1043 fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, tcx: TyCtxt<'_>) {
1044 for attr in it.attributes(tcx, false) {
1045 write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
1046 }
1047 }
1048
1049 #[derive(Copy, Clone)]
1050 enum AssocItemLink<'a> {
1051 Anchor(Option<&'a str>),
1052 GotoSource(ItemId, &'a FxHashSet<Symbol>),
1053 }
1054
1055 impl<'a> AssocItemLink<'a> {
anchor(&self, id: &'a str) -> Self1056 fn anchor(&self, id: &'a str) -> Self {
1057 match *self {
1058 AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(id)),
1059 ref other => *other,
1060 }
1061 }
1062 }
1063
write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str)1064 fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) {
1065 write!(
1066 w,
1067 "<h2 id=\"{id}\" class=\"small-section-header\">\
1068 {title}\
1069 <a href=\"#{id}\" class=\"anchor\">§</a>\
1070 </h2>"
1071 )
1072 .unwrap();
1073 }
1074
render_all_impls( mut w: impl Write, cx: &mut Context<'_>, containing_item: &clean::Item, concrete: &[&Impl], synthetic: &[&Impl], blanket_impl: &[&Impl], )1075 pub(crate) fn render_all_impls(
1076 mut w: impl Write,
1077 cx: &mut Context<'_>,
1078 containing_item: &clean::Item,
1079 concrete: &[&Impl],
1080 synthetic: &[&Impl],
1081 blanket_impl: &[&Impl],
1082 ) {
1083 let mut impls = Buffer::html();
1084 render_impls(cx, &mut impls, concrete, containing_item, true);
1085 let impls = impls.into_inner();
1086 if !impls.is_empty() {
1087 write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations");
1088 write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls).unwrap();
1089 }
1090
1091 if !synthetic.is_empty() {
1092 write_impl_section_heading(
1093 &mut w,
1094 "Auto Trait Implementations",
1095 "synthetic-implementations",
1096 );
1097 w.write_str("<div id=\"synthetic-implementations-list\">").unwrap();
1098 render_impls(cx, &mut w, synthetic, containing_item, false);
1099 w.write_str("</div>").unwrap();
1100 }
1101
1102 if !blanket_impl.is_empty() {
1103 write_impl_section_heading(&mut w, "Blanket Implementations", "blanket-implementations");
1104 w.write_str("<div id=\"blanket-implementations-list\">").unwrap();
1105 render_impls(cx, &mut w, blanket_impl, containing_item, false);
1106 w.write_str("</div>").unwrap();
1107 }
1108 }
1109
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>1110 fn render_assoc_items<'a, 'cx: 'a>(
1111 cx: &'a mut Context<'cx>,
1112 containing_item: &'a clean::Item,
1113 it: DefId,
1114 what: AssocItemRender<'a>,
1115 ) -> impl fmt::Display + 'a + Captures<'cx> {
1116 let mut derefs = DefIdSet::default();
1117 derefs.insert(it);
1118 display_fn(move |f| {
1119 render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs);
1120 Ok(())
1121 })
1122 }
1123
render_assoc_items_inner( mut w: &mut dyn fmt::Write, cx: &mut Context<'_>, containing_item: &clean::Item, it: DefId, what: AssocItemRender<'_>, derefs: &mut DefIdSet, )1124 fn render_assoc_items_inner(
1125 mut w: &mut dyn fmt::Write,
1126 cx: &mut Context<'_>,
1127 containing_item: &clean::Item,
1128 it: DefId,
1129 what: AssocItemRender<'_>,
1130 derefs: &mut DefIdSet,
1131 ) {
1132 info!("Documenting associated items of {:?}", containing_item.name);
1133 let shared = Rc::clone(&cx.shared);
1134 let cache = &shared.cache;
1135 let Some(v) = cache.impls.get(&it) else { return };
1136 let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
1137 if !non_trait.is_empty() {
1138 let mut tmp_buf = Buffer::html();
1139 let (render_mode, id, class_html) = match what {
1140 AssocItemRender::All => {
1141 write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations");
1142 (RenderMode::Normal, "implementations-list".to_owned(), "")
1143 }
1144 AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
1145 let id =
1146 cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
1147 if let Some(def_id) = type_.def_id(cx.cache()) {
1148 cx.deref_id_map.insert(def_id, id.clone());
1149 }
1150 write_impl_section_heading(
1151 &mut tmp_buf,
1152 &format!(
1153 "<span>Methods from {trait_}<Target = {type_}></span>",
1154 trait_ = trait_.print(cx),
1155 type_ = type_.print(cx),
1156 ),
1157 &id,
1158 );
1159 (
1160 RenderMode::ForDeref { mut_: deref_mut_ },
1161 cx.derive_id(id),
1162 r#" class="impl-items""#,
1163 )
1164 }
1165 };
1166 let mut impls_buf = Buffer::html();
1167 for i in &non_trait {
1168 render_impl(
1169 &mut impls_buf,
1170 cx,
1171 i,
1172 containing_item,
1173 AssocItemLink::Anchor(None),
1174 render_mode,
1175 None,
1176 &[],
1177 ImplRenderingParameters {
1178 show_def_docs: true,
1179 show_default_items: true,
1180 show_non_assoc_items: true,
1181 toggle_open_by_default: true,
1182 },
1183 );
1184 }
1185 if !impls_buf.is_empty() {
1186 write!(w, "{}", tmp_buf.into_inner()).unwrap();
1187 write!(w, "<div id=\"{id}\"{class_html}>").unwrap();
1188 write!(w, "{}", impls_buf.into_inner()).unwrap();
1189 w.write_str("</div>").unwrap();
1190 }
1191 }
1192
1193 if !traits.is_empty() {
1194 let deref_impl =
1195 traits.iter().find(|t| t.trait_did() == cx.tcx().lang_items().deref_trait());
1196 if let Some(impl_) = deref_impl {
1197 let has_deref_mut =
1198 traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
1199 render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs);
1200 }
1201
1202 // If we were already one level into rendering deref methods, we don't want to render
1203 // anything after recursing into any further deref methods above.
1204 if let AssocItemRender::DerefFor { .. } = what {
1205 return;
1206 }
1207
1208 let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
1209 traits.into_iter().partition(|t| t.inner_impl().kind.is_auto());
1210 let (blanket_impl, concrete): (Vec<&Impl>, _) =
1211 concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
1212
1213 render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl);
1214 }
1215 }
1216
render_deref_methods( mut w: impl Write, cx: &mut Context<'_>, impl_: &Impl, container_item: &clean::Item, deref_mut: bool, derefs: &mut DefIdSet, )1217 fn render_deref_methods(
1218 mut w: impl Write,
1219 cx: &mut Context<'_>,
1220 impl_: &Impl,
1221 container_item: &clean::Item,
1222 deref_mut: bool,
1223 derefs: &mut DefIdSet,
1224 ) {
1225 let cache = cx.cache();
1226 let deref_type = impl_.inner_impl().trait_.as_ref().unwrap();
1227 let (target, real_target) = impl_
1228 .inner_impl()
1229 .items
1230 .iter()
1231 .find_map(|item| match *item.kind {
1232 clean::AssocTypeItem(box ref t, _) => Some(match *t {
1233 clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
1234 _ => (&t.type_, &t.type_),
1235 }),
1236 _ => None,
1237 })
1238 .expect("Expected associated type binding");
1239 debug!("Render deref methods for {:#?}, target {:#?}", impl_.inner_impl().for_, target);
1240 let what =
1241 AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut };
1242 if let Some(did) = target.def_id(cache) {
1243 if let Some(type_did) = impl_.inner_impl().for_.def_id(cache) {
1244 // `impl Deref<Target = S> for S`
1245 if did == type_did || !derefs.insert(did) {
1246 // Avoid infinite cycles
1247 return;
1248 }
1249 }
1250 render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
1251 } else if let Some(prim) = target.primitive_type() {
1252 if let Some(&did) = cache.primitive_locations.get(&prim) {
1253 render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
1254 }
1255 }
1256 }
1257
should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool1258 fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool {
1259 let self_type_opt = match *item.kind {
1260 clean::MethodItem(ref method, _) => method.decl.self_type(),
1261 clean::TyMethodItem(ref method) => method.decl.self_type(),
1262 _ => None,
1263 };
1264
1265 if let Some(self_ty) = self_type_opt {
1266 let (by_mut_ref, by_box, by_value) = match self_ty {
1267 SelfTy::SelfBorrowed(_, mutability)
1268 | SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => {
1269 (mutability == Mutability::Mut, false, false)
1270 }
1271 SelfTy::SelfExplicit(clean::Type::Path { path }) => {
1272 (false, Some(path.def_id()) == tcx.lang_items().owned_box(), false)
1273 }
1274 SelfTy::SelfValue => (false, false, true),
1275 _ => (false, false, false),
1276 };
1277
1278 (deref_mut_ || !by_mut_ref) && !by_box && !by_value
1279 } else {
1280 false
1281 }
1282 }
1283
notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String>1284 pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String> {
1285 let mut has_notable_trait = false;
1286
1287 if ty.is_unit() {
1288 // Very common fast path.
1289 return None;
1290 }
1291
1292 let did = ty.def_id(cx.cache())?;
1293
1294 // Box has pass-through impls for Read, Write, Iterator, and Future when the
1295 // boxed type implements one of those. We don't want to treat every Box return
1296 // as being notably an Iterator (etc), though, so we exempt it. Pin has the same
1297 // issue, with a pass-through impl for Future.
1298 if Some(did) == cx.tcx().lang_items().owned_box()
1299 || Some(did) == cx.tcx().lang_items().pin_type()
1300 {
1301 return None;
1302 }
1303
1304 if let Some(impls) = cx.cache().impls.get(&did) {
1305 for i in impls {
1306 let impl_ = i.inner_impl();
1307 if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) {
1308 // Two different types might have the same did,
1309 // without actually being the same.
1310 continue;
1311 }
1312 if let Some(trait_) = &impl_.trait_ {
1313 let trait_did = trait_.def_id();
1314
1315 if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx()))
1316 {
1317 has_notable_trait = true;
1318 }
1319 }
1320 }
1321 }
1322
1323 if has_notable_trait {
1324 cx.types_with_notable_traits.insert(ty.clone());
1325 Some(format!(
1326 " <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
1327 ty = Escape(&format!("{:#}", ty.print(cx))),
1328 ))
1329 } else {
1330 None
1331 }
1332 }
1333
notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String)1334 fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
1335 let mut out = Buffer::html();
1336
1337 let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this");
1338
1339 let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this");
1340
1341 for i in impls {
1342 let impl_ = i.inner_impl();
1343 if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) {
1344 // Two different types might have the same did,
1345 // without actually being the same.
1346 continue;
1347 }
1348 if let Some(trait_) = &impl_.trait_ {
1349 let trait_did = trait_.def_id();
1350
1351 if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) {
1352 if out.is_empty() {
1353 write!(
1354 &mut out,
1355 "<h3>Notable traits for <code>{}</code></h3>\
1356 <pre><code>",
1357 impl_.for_.print(cx)
1358 );
1359 }
1360
1361 //use the "where" class here to make it small
1362 write!(
1363 &mut out,
1364 "<span class=\"where fmt-newline\">{}</span>",
1365 impl_.print(false, cx)
1366 );
1367 for it in &impl_.items {
1368 if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
1369 out.push_str("<span class=\"where fmt-newline\"> ");
1370 let empty_set = FxHashSet::default();
1371 let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set);
1372 assoc_type(
1373 &mut out,
1374 it,
1375 &tydef.generics,
1376 &[], // intentionally leaving out bounds
1377 Some(&tydef.type_),
1378 src_link,
1379 0,
1380 cx,
1381 );
1382 out.push_str(";</span>");
1383 }
1384 }
1385 }
1386 }
1387 }
1388 if out.is_empty() {
1389 write!(&mut out, "</code></pre>",);
1390 }
1391
1392 (format!("{:#}", ty.print(cx)), out.into_inner())
1393 }
1394
notable_traits_json<'a>( tys: impl Iterator<Item = &'a clean::Type>, cx: &Context<'_>, ) -> String1395 pub(crate) fn notable_traits_json<'a>(
1396 tys: impl Iterator<Item = &'a clean::Type>,
1397 cx: &Context<'_>,
1398 ) -> String {
1399 let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect();
1400 mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2));
1401 struct NotableTraitsMap(Vec<(String, String)>);
1402 impl Serialize for NotableTraitsMap {
1403 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1404 where
1405 S: Serializer,
1406 {
1407 let mut map = serializer.serialize_map(Some(self.0.len()))?;
1408 for item in &self.0 {
1409 map.serialize_entry(&item.0, &item.1)?;
1410 }
1411 map.end()
1412 }
1413 }
1414 serde_json::to_string(&NotableTraitsMap(mp))
1415 .expect("serialize (string, string) -> json object cannot fail")
1416 }
1417
1418 #[derive(Clone, Copy, Debug)]
1419 struct ImplRenderingParameters {
1420 show_def_docs: bool,
1421 show_default_items: bool,
1422 /// Whether or not to show methods.
1423 show_non_assoc_items: bool,
1424 toggle_open_by_default: bool,
1425 }
1426
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, )1427 fn render_impl(
1428 w: &mut Buffer,
1429 cx: &mut Context<'_>,
1430 i: &Impl,
1431 parent: &clean::Item,
1432 link: AssocItemLink<'_>,
1433 render_mode: RenderMode,
1434 use_absolute: Option<bool>,
1435 aliases: &[String],
1436 rendering_params: ImplRenderingParameters,
1437 ) {
1438 let shared = Rc::clone(&cx.shared);
1439 let cache = &shared.cache;
1440 let traits = &cache.traits;
1441 let trait_ = i.trait_did().map(|did| &traits[&did]);
1442 let mut close_tags = String::new();
1443
1444 // For trait implementations, the `interesting` output contains all methods that have doc
1445 // comments, and the `boring` output contains all methods that do not. The distinction is
1446 // used to allow hiding the boring methods.
1447 // `containing_item` is used for rendering stability info. If the parent is a trait impl,
1448 // `containing_item` will the grandparent, since trait impls can't have stability attached.
1449 fn doc_impl_item(
1450 boring: &mut Buffer,
1451 interesting: &mut Buffer,
1452 cx: &mut Context<'_>,
1453 item: &clean::Item,
1454 parent: &clean::Item,
1455 containing_item: &clean::Item,
1456 link: AssocItemLink<'_>,
1457 render_mode: RenderMode,
1458 is_default_item: bool,
1459 trait_: Option<&clean::Trait>,
1460 rendering_params: ImplRenderingParameters,
1461 ) {
1462 let item_type = item.type_();
1463 let name = item.name.as_ref().unwrap();
1464
1465 let render_method_item = rendering_params.show_non_assoc_items
1466 && match render_mode {
1467 RenderMode::Normal => true,
1468 RenderMode::ForDeref { mut_: deref_mut_ } => {
1469 should_render_item(item, deref_mut_, cx.tcx())
1470 }
1471 };
1472
1473 let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" };
1474
1475 let mut doc_buffer = Buffer::empty_from(boring);
1476 let mut info_buffer = Buffer::empty_from(boring);
1477 let mut short_documented = true;
1478
1479 if render_method_item {
1480 if !is_default_item {
1481 if let Some(t) = trait_ {
1482 // The trait item may have been stripped so we might not
1483 // find any documentation or stability for it.
1484 if let Some(it) = t.items.iter().find(|i| i.name == item.name) {
1485 // We need the stability of the item from the trait
1486 // because impls can't have a stability.
1487 if !item.doc_value().is_empty() {
1488 document_item_info(cx, it, Some(parent))
1489 .render_into(&mut info_buffer)
1490 .unwrap();
1491 write!(
1492 &mut doc_buffer,
1493 "{}",
1494 document_full(item, cx, HeadingOffset::H5)
1495 );
1496 short_documented = false;
1497 } else {
1498 // In case the item isn't documented,
1499 // provide short documentation from the trait.
1500 write!(
1501 &mut doc_buffer,
1502 "{}",
1503 document_short(
1504 it,
1505 cx,
1506 link,
1507 parent,
1508 rendering_params.show_def_docs,
1509 )
1510 );
1511 }
1512 }
1513 } else {
1514 document_item_info(cx, item, Some(parent))
1515 .render_into(&mut info_buffer)
1516 .unwrap();
1517 if rendering_params.show_def_docs {
1518 write!(&mut doc_buffer, "{}", document_full(item, cx, HeadingOffset::H5));
1519 short_documented = false;
1520 }
1521 }
1522 } else {
1523 write!(
1524 &mut doc_buffer,
1525 "{}",
1526 document_short(item, cx, link, parent, rendering_params.show_def_docs,)
1527 );
1528 }
1529 }
1530 let w = if short_documented && trait_.is_some() { interesting } else { boring };
1531
1532 let toggled = !doc_buffer.is_empty();
1533 if toggled {
1534 let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" };
1535 write!(w, "<details class=\"toggle{}\" open><summary>", method_toggle_class);
1536 }
1537 match &*item.kind {
1538 clean::MethodItem(..) | clean::TyMethodItem(_) => {
1539 // Only render when the method is not static or we allow static methods
1540 if render_method_item {
1541 let id = cx.derive_id(format!("{}.{}", item_type, name));
1542 let source_id = trait_
1543 .and_then(|trait_| {
1544 trait_.items.iter().find(|item| {
1545 item.name.map(|n| n.as_str().eq(name.as_str())).unwrap_or(false)
1546 })
1547 })
1548 .map(|item| format!("{}.{}", item.type_(), name));
1549 write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class,);
1550 render_rightside(w, cx, item, containing_item, render_mode);
1551 if trait_.is_some() {
1552 // Anchors are only used on trait impls.
1553 write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
1554 }
1555 w.write_str("<h4 class=\"code-header\">");
1556 render_assoc_item(
1557 w,
1558 item,
1559 link.anchor(source_id.as_ref().unwrap_or(&id)),
1560 ItemType::Impl,
1561 cx,
1562 render_mode,
1563 );
1564 w.write_str("</h4>");
1565 w.write_str("</section>");
1566 }
1567 }
1568 kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => {
1569 let source_id = format!("{}.{}", item_type, name);
1570 let id = cx.derive_id(source_id.clone());
1571 write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class);
1572 render_rightside(w, cx, item, containing_item, render_mode);
1573 if trait_.is_some() {
1574 // Anchors are only used on trait impls.
1575 write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
1576 }
1577 w.write_str("<h4 class=\"code-header\">");
1578 assoc_const(
1579 w,
1580 item,
1581 ty,
1582 match kind {
1583 clean::TyAssocConstItem(_) => None,
1584 clean::AssocConstItem(_, default) => Some(default),
1585 _ => unreachable!(),
1586 },
1587 link.anchor(if trait_.is_some() { &source_id } else { &id }),
1588 "",
1589 cx,
1590 );
1591 w.write_str("</h4>");
1592 w.write_str("</section>");
1593 }
1594 clean::TyAssocTypeItem(generics, bounds) => {
1595 let source_id = format!("{}.{}", item_type, name);
1596 let id = cx.derive_id(source_id.clone());
1597 write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class);
1598 if trait_.is_some() {
1599 // Anchors are only used on trait impls.
1600 write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
1601 }
1602 w.write_str("<h4 class=\"code-header\">");
1603 assoc_type(
1604 w,
1605 item,
1606 generics,
1607 bounds,
1608 None,
1609 link.anchor(if trait_.is_some() { &source_id } else { &id }),
1610 0,
1611 cx,
1612 );
1613 w.write_str("</h4>");
1614 w.write_str("</section>");
1615 }
1616 clean::AssocTypeItem(tydef, _bounds) => {
1617 let source_id = format!("{}.{}", item_type, name);
1618 let id = cx.derive_id(source_id.clone());
1619 write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class);
1620 if trait_.is_some() {
1621 // Anchors are only used on trait impls.
1622 write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
1623 }
1624 w.write_str("<h4 class=\"code-header\">");
1625 assoc_type(
1626 w,
1627 item,
1628 &tydef.generics,
1629 &[], // intentionally leaving out bounds
1630 Some(tydef.item_type.as_ref().unwrap_or(&tydef.type_)),
1631 link.anchor(if trait_.is_some() { &source_id } else { &id }),
1632 0,
1633 cx,
1634 );
1635 w.write_str("</h4>");
1636 w.write_str("</section>");
1637 }
1638 clean::StrippedItem(..) => return,
1639 _ => panic!("can't make docs for trait item with name {:?}", item.name),
1640 }
1641
1642 w.push_buffer(info_buffer);
1643 if toggled {
1644 w.write_str("</summary>");
1645 w.push_buffer(doc_buffer);
1646 w.push_str("</details>");
1647 }
1648 }
1649
1650 let mut impl_items = Buffer::empty_from(w);
1651 let mut default_impl_items = Buffer::empty_from(w);
1652
1653 for trait_item in &i.inner_impl().items {
1654 doc_impl_item(
1655 &mut default_impl_items,
1656 &mut impl_items,
1657 cx,
1658 trait_item,
1659 if trait_.is_some() { &i.impl_item } else { parent },
1660 parent,
1661 link,
1662 render_mode,
1663 false,
1664 trait_,
1665 rendering_params,
1666 );
1667 }
1668
1669 fn render_default_items(
1670 boring: &mut Buffer,
1671 interesting: &mut Buffer,
1672 cx: &mut Context<'_>,
1673 t: &clean::Trait,
1674 i: &clean::Impl,
1675 parent: &clean::Item,
1676 containing_item: &clean::Item,
1677 render_mode: RenderMode,
1678 rendering_params: ImplRenderingParameters,
1679 ) {
1680 for trait_item in &t.items {
1681 // Skip over any default trait items that are impossible to call
1682 // (e.g. if it has a `Self: Sized` bound on an unsized type).
1683 if let Some(impl_def_id) = parent.item_id.as_def_id()
1684 && let Some(trait_item_def_id) = trait_item.item_id.as_def_id()
1685 && cx.tcx().is_impossible_method((impl_def_id, trait_item_def_id))
1686 {
1687 continue;
1688 }
1689
1690 let n = trait_item.name;
1691 if i.items.iter().any(|m| m.name == n) {
1692 continue;
1693 }
1694 let did = i.trait_.as_ref().unwrap().def_id();
1695 let provided_methods = i.provided_trait_methods(cx.tcx());
1696 let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods);
1697
1698 doc_impl_item(
1699 boring,
1700 interesting,
1701 cx,
1702 trait_item,
1703 parent,
1704 containing_item,
1705 assoc_link,
1706 render_mode,
1707 true,
1708 Some(t),
1709 rendering_params,
1710 );
1711 }
1712 }
1713
1714 // If we've implemented a trait, then also emit documentation for all
1715 // default items which weren't overridden in the implementation block.
1716 // We don't emit documentation for default items if they appear in the
1717 // Implementations on Foreign Types or Implementors sections.
1718 if rendering_params.show_default_items {
1719 if let Some(t) = trait_ {
1720 render_default_items(
1721 &mut default_impl_items,
1722 &mut impl_items,
1723 cx,
1724 t,
1725 i.inner_impl(),
1726 &i.impl_item,
1727 parent,
1728 render_mode,
1729 rendering_params,
1730 );
1731 }
1732 }
1733 if render_mode == RenderMode::Normal {
1734 let toggled = !(impl_items.is_empty() && default_impl_items.is_empty());
1735 if toggled {
1736 close_tags.insert_str(0, "</details>");
1737 write!(
1738 w,
1739 "<details class=\"toggle implementors-toggle\"{}>",
1740 if rendering_params.toggle_open_by_default { " open" } else { "" }
1741 );
1742 write!(w, "<summary>")
1743 }
1744 render_impl_summary(
1745 w,
1746 cx,
1747 i,
1748 parent,
1749 parent,
1750 rendering_params.show_def_docs,
1751 use_absolute,
1752 aliases,
1753 );
1754 if toggled {
1755 write!(w, "</summary>")
1756 }
1757
1758 if let Some(ref dox) = i.impl_item.opt_doc_value() {
1759 if trait_.is_none() && i.inner_impl().items.is_empty() {
1760 w.write_str(
1761 "<div class=\"item-info\">\
1762 <div class=\"stab empty-impl\">This impl block contains no items.</div>\
1763 </div>",
1764 );
1765 }
1766 write!(
1767 w,
1768 "<div class=\"docblock\">{}</div>",
1769 Markdown {
1770 content: &*dox,
1771 links: &i.impl_item.links(cx),
1772 ids: &mut cx.id_map,
1773 error_codes: cx.shared.codes,
1774 edition: cx.shared.edition(),
1775 playground: &cx.shared.playground,
1776 heading_offset: HeadingOffset::H4
1777 }
1778 .into_string()
1779 );
1780 }
1781 if !default_impl_items.is_empty() || !impl_items.is_empty() {
1782 w.write_str("<div class=\"impl-items\">");
1783 close_tags.insert_str(0, "</div>");
1784 }
1785 }
1786 if !default_impl_items.is_empty() || !impl_items.is_empty() {
1787 w.push_buffer(default_impl_items);
1788 w.push_buffer(impl_items);
1789 }
1790 w.write_str(&close_tags);
1791 }
1792
1793 // Render the items that appear on the right side of methods, impls, and
1794 // associated types. For example "1.0.0 (const: 1.39.0) · source".
render_rightside( w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, containing_item: &clean::Item, render_mode: RenderMode, )1795 fn render_rightside(
1796 w: &mut Buffer,
1797 cx: &Context<'_>,
1798 item: &clean::Item,
1799 containing_item: &clean::Item,
1800 render_mode: RenderMode,
1801 ) {
1802 let tcx = cx.tcx();
1803
1804 // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
1805 // this condition.
1806 let (const_stability, const_stable_since) = match render_mode {
1807 RenderMode::Normal => (item.const_stability(tcx), containing_item.const_stable_since(tcx)),
1808 RenderMode::ForDeref { .. } => (None, None),
1809 };
1810 let src_href = cx.src_href(item);
1811 let has_src_ref = src_href.is_some();
1812
1813 let mut rightside = Buffer::new();
1814 let has_stability = render_stability_since_raw_with_extra(
1815 &mut rightside,
1816 item.stable_since(tcx),
1817 const_stability,
1818 containing_item.stable_since(tcx),
1819 const_stable_since,
1820 if has_src_ref { "" } else { " rightside" },
1821 );
1822 if let Some(l) = src_href {
1823 if has_stability {
1824 write!(rightside, " · <a class=\"srclink\" href=\"{}\">source</a>", l)
1825 } else {
1826 write!(rightside, "<a class=\"srclink rightside\" href=\"{}\">source</a>", l)
1827 }
1828 }
1829 if has_stability && has_src_ref {
1830 write!(w, "<span class=\"rightside\">{}</span>", rightside.into_inner());
1831 } else {
1832 w.push_buffer(rightside);
1833 }
1834 }
1835
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<bool>, aliases: &[String], )1836 pub(crate) fn render_impl_summary(
1837 w: &mut Buffer,
1838 cx: &mut Context<'_>,
1839 i: &Impl,
1840 parent: &clean::Item,
1841 containing_item: &clean::Item,
1842 show_def_docs: bool,
1843 use_absolute: Option<bool>,
1844 // This argument is used to reference same type with different paths to avoid duplication
1845 // in documentation pages for trait with automatic implementations like "Send" and "Sync".
1846 aliases: &[String],
1847 ) {
1848 let inner_impl = i.inner_impl();
1849 let id = cx.derive_id(get_id_for_impl(&inner_impl.for_, inner_impl.trait_.as_ref(), cx));
1850 let aliases = if aliases.is_empty() {
1851 String::new()
1852 } else {
1853 format!(" data-aliases=\"{}\"", aliases.join(","))
1854 };
1855 write!(w, "<section id=\"{}\" class=\"impl\"{}>", id, aliases);
1856 render_rightside(w, cx, &i.impl_item, containing_item, RenderMode::Normal);
1857 write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
1858 write!(w, "<h3 class=\"code-header\">");
1859
1860 if let Some(use_absolute) = use_absolute {
1861 write!(w, "{}", inner_impl.print(use_absolute, cx));
1862 if show_def_docs {
1863 for it in &inner_impl.items {
1864 if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
1865 w.write_str("<span class=\"where fmt-newline\"> ");
1866 assoc_type(
1867 w,
1868 it,
1869 &tydef.generics,
1870 &[], // intentionally leaving out bounds
1871 Some(&tydef.type_),
1872 AssocItemLink::Anchor(None),
1873 0,
1874 cx,
1875 );
1876 w.write_str(";</span>");
1877 }
1878 }
1879 }
1880 } else {
1881 write!(w, "{}", inner_impl.print(false, cx));
1882 }
1883 write!(w, "</h3>");
1884
1885 let is_trait = inner_impl.trait_.is_some();
1886 if is_trait {
1887 if let Some(portability) = portability(&i.impl_item, Some(parent)) {
1888 write!(
1889 w,
1890 "<span class=\"item-info\"><div class=\"stab portability\">{}</div></span>",
1891 portability
1892 );
1893 }
1894 }
1895
1896 w.write_str("</section>");
1897 }
1898
small_url_encode(s: String) -> String1899 pub(crate) fn small_url_encode(s: String) -> String {
1900 // These characters don't need to be escaped in a URI.
1901 // See https://url.spec.whatwg.org/#query-percent-encode-set
1902 // and https://url.spec.whatwg.org/#urlencoded-parsing
1903 // and https://url.spec.whatwg.org/#url-code-points
1904 fn dont_escape(c: u8) -> bool {
1905 (b'a' <= c && c <= b'z')
1906 || (b'A' <= c && c <= b'Z')
1907 || (b'0' <= c && c <= b'9')
1908 || c == b'-'
1909 || c == b'_'
1910 || c == b'.'
1911 || c == b','
1912 || c == b'~'
1913 || c == b'!'
1914 || c == b'\''
1915 || c == b'('
1916 || c == b')'
1917 || c == b'*'
1918 || c == b'/'
1919 || c == b';'
1920 || c == b':'
1921 || c == b'?'
1922 // As described in urlencoded-parsing, the
1923 // first `=` is the one that separates key from
1924 // value. Following `=`s are part of the value.
1925 || c == b'='
1926 }
1927 let mut st = String::new();
1928 let mut last_match = 0;
1929 for (idx, b) in s.bytes().enumerate() {
1930 if dont_escape(b) {
1931 continue;
1932 }
1933
1934 if last_match != idx {
1935 // Invariant: `idx` must be the first byte in a character at this point.
1936 st += &s[last_match..idx];
1937 }
1938 if b == b' ' {
1939 // URL queries are decoded with + replaced with SP.
1940 // While the same is not true for hashes, rustdoc only needs to be
1941 // consistent with itself when encoding them.
1942 st += "+";
1943 } else {
1944 write!(st, "%{:02X}", b).unwrap();
1945 }
1946 // Invariant: if the current byte is not at the start of a multi-byte character,
1947 // we need to get down here so that when the next turn of the loop comes around,
1948 // last_match winds up equalling idx.
1949 //
1950 // In other words, dont_escape must always return `false` in multi-byte character.
1951 last_match = idx + 1;
1952 }
1953
1954 if last_match != 0 {
1955 st += &s[last_match..];
1956 st
1957 } else {
1958 s
1959 }
1960 }
1961
get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String1962 fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String {
1963 match trait_ {
1964 Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))),
1965 None => small_url_encode(format!("impl-{:#}", for_.print(cx))),
1966 }
1967 }
1968
extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)>1969 fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
1970 match *item.kind {
1971 clean::ItemKind::ImplItem(ref i) => {
1972 i.trait_.as_ref().map(|trait_| {
1973 // Alternative format produces no URLs,
1974 // so this parameter does nothing.
1975 (format!("{:#}", i.for_.print(cx)), get_id_for_impl(&i.for_, Some(trait_), cx))
1976 })
1977 }
1978 _ => None,
1979 }
1980 }
1981
1982 /// Returns the list of implementations for the primitive reference type, filtering out any
1983 /// implementations that are on concrete or partially generic types, only keeping implementations
1984 /// of the form `impl<T> Trait for &T`.
get_filtered_impls_for_reference<'a>( shared: &'a Rc<SharedContext<'_>>, it: &clean::Item, ) -> (Vec<&'a Impl>, Vec<&'a Impl>, Vec<&'a Impl>)1985 pub(crate) fn get_filtered_impls_for_reference<'a>(
1986 shared: &'a Rc<SharedContext<'_>>,
1987 it: &clean::Item,
1988 ) -> (Vec<&'a Impl>, Vec<&'a Impl>, Vec<&'a Impl>) {
1989 let def_id = it.item_id.expect_def_id();
1990 // If the reference primitive is somehow not defined, exit early.
1991 let Some(v) = shared.cache.impls.get(&def_id) else { return (Vec::new(), Vec::new(), Vec::new()) };
1992 // Since there is no "direct implementation" on the reference primitive type, we filter out
1993 // every implementation which isn't a trait implementation.
1994 let traits = v.iter().filter(|i| i.inner_impl().trait_.is_some());
1995 let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
1996 traits.partition(|t| t.inner_impl().kind.is_auto());
1997
1998 let (blanket_impl, concrete): (Vec<&Impl>, _) =
1999 concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
2000 // Now we keep only references over full generic types.
2001 let concrete: Vec<_> = concrete
2002 .into_iter()
2003 .filter(|t| match t.inner_impl().for_ {
2004 clean::Type::BorrowedRef { ref type_, .. } => type_.is_full_generic(),
2005 _ => false,
2006 })
2007 .collect();
2008
2009 (concrete, synthetic, blanket_impl)
2010 }
2011
2012 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
2013 pub(crate) enum ItemSection {
2014 Reexports,
2015 PrimitiveTypes,
2016 Modules,
2017 Macros,
2018 Structs,
2019 Enums,
2020 Constants,
2021 Statics,
2022 Traits,
2023 Functions,
2024 TypeDefinitions,
2025 Unions,
2026 Implementations,
2027 TypeMethods,
2028 Methods,
2029 StructFields,
2030 Variants,
2031 AssociatedTypes,
2032 AssociatedConstants,
2033 ForeignTypes,
2034 Keywords,
2035 OpaqueTypes,
2036 AttributeMacros,
2037 DeriveMacros,
2038 TraitAliases,
2039 }
2040
2041 impl ItemSection {
2042 const ALL: &'static [Self] = {
2043 use ItemSection::*;
2044 // NOTE: The order here affects the order in the UI.
2045 &[
2046 Reexports,
2047 PrimitiveTypes,
2048 Modules,
2049 Macros,
2050 Structs,
2051 Enums,
2052 Constants,
2053 Statics,
2054 Traits,
2055 Functions,
2056 TypeDefinitions,
2057 Unions,
2058 Implementations,
2059 TypeMethods,
2060 Methods,
2061 StructFields,
2062 Variants,
2063 AssociatedTypes,
2064 AssociatedConstants,
2065 ForeignTypes,
2066 Keywords,
2067 OpaqueTypes,
2068 AttributeMacros,
2069 DeriveMacros,
2070 TraitAliases,
2071 ]
2072 };
2073
id(self) -> &'static str2074 fn id(self) -> &'static str {
2075 match self {
2076 Self::Reexports => "reexports",
2077 Self::Modules => "modules",
2078 Self::Structs => "structs",
2079 Self::Unions => "unions",
2080 Self::Enums => "enums",
2081 Self::Functions => "functions",
2082 Self::TypeDefinitions => "types",
2083 Self::Statics => "statics",
2084 Self::Constants => "constants",
2085 Self::Traits => "traits",
2086 Self::Implementations => "impls",
2087 Self::TypeMethods => "tymethods",
2088 Self::Methods => "methods",
2089 Self::StructFields => "fields",
2090 Self::Variants => "variants",
2091 Self::Macros => "macros",
2092 Self::PrimitiveTypes => "primitives",
2093 Self::AssociatedTypes => "associated-types",
2094 Self::AssociatedConstants => "associated-consts",
2095 Self::ForeignTypes => "foreign-types",
2096 Self::Keywords => "keywords",
2097 Self::OpaqueTypes => "opaque-types",
2098 Self::AttributeMacros => "attributes",
2099 Self::DeriveMacros => "derives",
2100 Self::TraitAliases => "trait-aliases",
2101 }
2102 }
2103
name(self) -> &'static str2104 fn name(self) -> &'static str {
2105 match self {
2106 Self::Reexports => "Re-exports",
2107 Self::Modules => "Modules",
2108 Self::Structs => "Structs",
2109 Self::Unions => "Unions",
2110 Self::Enums => "Enums",
2111 Self::Functions => "Functions",
2112 Self::TypeDefinitions => "Type Definitions",
2113 Self::Statics => "Statics",
2114 Self::Constants => "Constants",
2115 Self::Traits => "Traits",
2116 Self::Implementations => "Implementations",
2117 Self::TypeMethods => "Type Methods",
2118 Self::Methods => "Methods",
2119 Self::StructFields => "Struct Fields",
2120 Self::Variants => "Variants",
2121 Self::Macros => "Macros",
2122 Self::PrimitiveTypes => "Primitive Types",
2123 Self::AssociatedTypes => "Associated Types",
2124 Self::AssociatedConstants => "Associated Constants",
2125 Self::ForeignTypes => "Foreign Types",
2126 Self::Keywords => "Keywords",
2127 Self::OpaqueTypes => "Opaque Types",
2128 Self::AttributeMacros => "Attribute Macros",
2129 Self::DeriveMacros => "Derive Macros",
2130 Self::TraitAliases => "Trait Aliases",
2131 }
2132 }
2133 }
2134
item_ty_to_section(ty: ItemType) -> ItemSection2135 fn item_ty_to_section(ty: ItemType) -> ItemSection {
2136 match ty {
2137 ItemType::ExternCrate | ItemType::Import => ItemSection::Reexports,
2138 ItemType::Module => ItemSection::Modules,
2139 ItemType::Struct => ItemSection::Structs,
2140 ItemType::Union => ItemSection::Unions,
2141 ItemType::Enum => ItemSection::Enums,
2142 ItemType::Function => ItemSection::Functions,
2143 ItemType::Typedef => ItemSection::TypeDefinitions,
2144 ItemType::Static => ItemSection::Statics,
2145 ItemType::Constant => ItemSection::Constants,
2146 ItemType::Trait => ItemSection::Traits,
2147 ItemType::Impl => ItemSection::Implementations,
2148 ItemType::TyMethod => ItemSection::TypeMethods,
2149 ItemType::Method => ItemSection::Methods,
2150 ItemType::StructField => ItemSection::StructFields,
2151 ItemType::Variant => ItemSection::Variants,
2152 ItemType::Macro => ItemSection::Macros,
2153 ItemType::Primitive => ItemSection::PrimitiveTypes,
2154 ItemType::AssocType => ItemSection::AssociatedTypes,
2155 ItemType::AssocConst => ItemSection::AssociatedConstants,
2156 ItemType::ForeignType => ItemSection::ForeignTypes,
2157 ItemType::Keyword => ItemSection::Keywords,
2158 ItemType::OpaqueTy => ItemSection::OpaqueTypes,
2159 ItemType::ProcAttribute => ItemSection::AttributeMacros,
2160 ItemType::ProcDerive => ItemSection::DeriveMacros,
2161 ItemType::TraitAlias => ItemSection::TraitAliases,
2162 }
2163 }
2164
2165 /// Returns a list of all paths used in the type.
2166 /// This is used to help deduplicate imported impls
2167 /// for reexported types. If any of the contained
2168 /// types are re-exported, we don't use the corresponding
2169 /// entry from the js file, as inlining will have already
2170 /// picked up the impl
collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String>2171 fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
2172 let mut out = Vec::new();
2173 let mut visited = FxHashSet::default();
2174 let mut work = VecDeque::new();
2175
2176 let mut process_path = |did: DefId| {
2177 let get_extern = || cache.external_paths.get(&did).map(|s| &s.0);
2178 let fqp = cache.exact_paths.get(&did).or_else(get_extern);
2179
2180 if let Some(path) = fqp {
2181 out.push(join_with_double_colon(&path));
2182 }
2183 };
2184
2185 work.push_back(first_ty);
2186
2187 while let Some(ty) = work.pop_front() {
2188 if !visited.insert(ty.clone()) {
2189 continue;
2190 }
2191
2192 match ty {
2193 clean::Type::Path { path } => process_path(path.def_id()),
2194 clean::Type::Tuple(tys) => {
2195 work.extend(tys.into_iter());
2196 }
2197 clean::Type::Slice(ty) => {
2198 work.push_back(*ty);
2199 }
2200 clean::Type::Array(ty, _) => {
2201 work.push_back(*ty);
2202 }
2203 clean::Type::RawPointer(_, ty) => {
2204 work.push_back(*ty);
2205 }
2206 clean::Type::BorrowedRef { type_, .. } => {
2207 work.push_back(*type_);
2208 }
2209 clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => {
2210 work.push_back(self_type);
2211 if let Some(trait_) = trait_ {
2212 process_path(trait_.def_id());
2213 }
2214 }
2215 _ => {}
2216 }
2217 }
2218 out
2219 }
2220
2221 const MAX_FULL_EXAMPLES: usize = 5;
2222 const NUM_VISIBLE_LINES: usize = 10;
2223
2224 /// Generates the HTML for example call locations generated via the --scrape-examples flag.
render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &clean::Item)2225 fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &clean::Item) {
2226 let tcx = cx.tcx();
2227 let def_id = item.item_id.expect_def_id();
2228 let key = tcx.def_path_hash(def_id);
2229 let Some(call_locations) = cx.shared.call_locations.get(&key) else { return };
2230
2231 // Generate a unique ID so users can link to this section for a given method
2232 let id = cx.id_map.derive("scraped-examples");
2233 write!(
2234 &mut w,
2235 "<div class=\"docblock scraped-example-list\">\
2236 <span></span>\
2237 <h5 id=\"{id}\">\
2238 <a href=\"#{id}\">Examples found in repository</a>\
2239 <a class=\"scrape-help\" href=\"{root_path}scrape-examples-help.html\">?</a>\
2240 </h5>",
2241 root_path = cx.root_path(),
2242 id = id
2243 )
2244 .unwrap();
2245
2246 // Create a URL to a particular location in a reverse-dependency's source file
2247 let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) {
2248 let (line_lo, line_hi) = loc.call_expr.line_span;
2249 let (anchor, title) = if line_lo == line_hi {
2250 ((line_lo + 1).to_string(), format!("line {}", line_lo + 1))
2251 } else {
2252 (
2253 format!("{}-{}", line_lo + 1, line_hi + 1),
2254 format!("lines {}-{}", line_lo + 1, line_hi + 1),
2255 )
2256 };
2257 let url = format!("{}{}#{}", cx.root_path(), call_data.url, anchor);
2258 (url, title)
2259 };
2260
2261 // Generate the HTML for a single example, being the title and code block
2262 let write_example = |mut w: &mut W, (path, call_data): (&PathBuf, &CallData)| -> bool {
2263 let contents = match fs::read_to_string(&path) {
2264 Ok(contents) => contents,
2265 Err(err) => {
2266 let span = item.span(tcx).map_or(rustc_span::DUMMY_SP, |span| span.inner());
2267 tcx.sess.span_err(span, format!("failed to read file {}: {}", path.display(), err));
2268 return false;
2269 }
2270 };
2271
2272 // To reduce file sizes, we only want to embed the source code needed to understand the example, not
2273 // the entire file. So we find the smallest byte range that covers all items enclosing examples.
2274 assert!(!call_data.locations.is_empty());
2275 let min_loc =
2276 call_data.locations.iter().min_by_key(|loc| loc.enclosing_item.byte_span.0).unwrap();
2277 let byte_min = min_loc.enclosing_item.byte_span.0;
2278 let line_min = min_loc.enclosing_item.line_span.0;
2279 let max_loc =
2280 call_data.locations.iter().max_by_key(|loc| loc.enclosing_item.byte_span.1).unwrap();
2281 let byte_max = max_loc.enclosing_item.byte_span.1;
2282 let line_max = max_loc.enclosing_item.line_span.1;
2283
2284 // The output code is limited to that byte range.
2285 let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)];
2286
2287 // The call locations need to be updated to reflect that the size of the program has changed.
2288 // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point.
2289 let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data
2290 .locations
2291 .iter()
2292 .map(|loc| {
2293 let (byte_lo, byte_hi) = loc.call_ident.byte_span;
2294 let (line_lo, line_hi) = loc.call_expr.line_span;
2295 let byte_range = (byte_lo - byte_min, byte_hi - byte_min);
2296
2297 let line_range = (line_lo - line_min, line_hi - line_min);
2298 let (line_url, line_title) = link_to_loc(call_data, loc);
2299
2300 (byte_range, (line_range, line_url, line_title))
2301 })
2302 .unzip();
2303
2304 let (_, init_url, init_title) = &line_ranges[0];
2305 let needs_expansion = line_max - line_min > NUM_VISIBLE_LINES;
2306 let locations_encoded = serde_json::to_string(&line_ranges).unwrap();
2307
2308 write!(
2309 &mut w,
2310 "<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\
2311 <div class=\"scraped-example-title\">\
2312 {name} (<a href=\"{url}\">{title}</a>)\
2313 </div>\
2314 <div class=\"code-wrapper\">",
2315 expanded_cls = if needs_expansion { "" } else { "expanded" },
2316 name = call_data.display_name,
2317 url = init_url,
2318 title = init_title,
2319 // The locations are encoded as a data attribute, so they can be read
2320 // later by the JS for interactions.
2321 locations = Escape(&locations_encoded)
2322 )
2323 .unwrap();
2324
2325 if line_ranges.len() > 1 {
2326 write!(w, r#"<button class="prev">≺</button> <button class="next">≻</button>"#)
2327 .unwrap();
2328 }
2329
2330 // Look for the example file in the source map if it exists, otherwise return a dummy span
2331 let file_span = (|| {
2332 let source_map = tcx.sess.source_map();
2333 let crate_src = tcx.sess.local_crate_source_file()?;
2334 let abs_crate_src = crate_src.canonicalize().ok()?;
2335 let crate_root = abs_crate_src.parent()?.parent()?;
2336 let rel_path = path.strip_prefix(crate_root).ok()?;
2337 let files = source_map.files();
2338 let file = files.iter().find(|file| match &file.name {
2339 FileName::Real(RealFileName::LocalPath(other_path)) => rel_path == other_path,
2340 _ => false,
2341 })?;
2342 Some(rustc_span::Span::with_root_ctxt(
2343 file.start_pos + BytePos(byte_min),
2344 file.start_pos + BytePos(byte_max),
2345 ))
2346 })()
2347 .unwrap_or(rustc_span::DUMMY_SP);
2348
2349 let mut decoration_info = FxHashMap::default();
2350 decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
2351 decoration_info.insert("highlight", byte_ranges);
2352
2353 sources::print_src(
2354 &mut w,
2355 contents_subset,
2356 file_span,
2357 cx,
2358 &cx.root_path(),
2359 highlight::DecorationInfo(decoration_info),
2360 sources::SourceContext::Embedded { offset: line_min, needs_expansion },
2361 );
2362 write!(w, "</div></div>").unwrap();
2363
2364 true
2365 };
2366
2367 // The call locations are output in sequence, so that sequence needs to be determined.
2368 // Ideally the most "relevant" examples would be shown first, but there's no general algorithm
2369 // for determining relevance. We instead proxy relevance with the following heuristics:
2370 // 1. Code written to be an example is better than code not written to be an example, e.g.
2371 // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo
2372 // directory structure in Rustdoc, so we proxy this by prioritizing code that comes from
2373 // a --crate-type bin.
2374 // 2. Smaller examples are better than large examples. So we prioritize snippets that have
2375 // the smallest number of lines in their enclosing item.
2376 // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the
2377 // ordering of examples from randomly changing between Rustdoc invocations.
2378 let ordered_locations = {
2379 fn sort_criterion<'a>(
2380 (_, call_data): &(&PathBuf, &'a CallData),
2381 ) -> (bool, u32, &'a String) {
2382 // Use the first location because that's what the user will see initially
2383 let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
2384 (!call_data.is_bin, hi - lo, &call_data.display_name)
2385 }
2386
2387 let mut locs = call_locations.iter().collect::<Vec<_>>();
2388 locs.sort_by_key(sort_criterion);
2389 locs
2390 };
2391
2392 let mut it = ordered_locations.into_iter().peekable();
2393
2394 // An example may fail to write if its source can't be read for some reason, so this method
2395 // continues iterating until a write succeeds
2396 let write_and_skip_failure = |w: &mut W, it: &mut Peekable<_>| {
2397 while let Some(example) = it.next() {
2398 if write_example(&mut *w, example) {
2399 break;
2400 }
2401 }
2402 };
2403
2404 // Write just one example that's visible by default in the method's description.
2405 write_and_skip_failure(&mut w, &mut it);
2406
2407 // Then add the remaining examples in a hidden section.
2408 if it.peek().is_some() {
2409 write!(
2410 w,
2411 "<details class=\"toggle more-examples-toggle\">\
2412 <summary class=\"hideme\">\
2413 <span>More examples</span>\
2414 </summary>\
2415 <div class=\"hide-more\">Hide additional examples</div>\
2416 <div class=\"more-scraped-examples\">\
2417 <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>"
2418 )
2419 .unwrap();
2420
2421 // Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
2422 // make the page arbitrarily huge!
2423 for _ in 0..MAX_FULL_EXAMPLES {
2424 write_and_skip_failure(&mut w, &mut it);
2425 }
2426
2427 // For the remaining examples, generate a <ul> containing links to the source files.
2428 if it.peek().is_some() {
2429 write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#)
2430 .unwrap();
2431 it.for_each(|(_, call_data)| {
2432 let (url, _) = link_to_loc(call_data, &call_data.locations[0]);
2433 write!(
2434 w,
2435 r#"<li><a href="{url}">{name}</a></li>"#,
2436 url = url,
2437 name = call_data.display_name
2438 )
2439 .unwrap();
2440 });
2441 write!(w, "</ul></div>").unwrap();
2442 }
2443
2444 write!(w, "</div></details>").unwrap();
2445 }
2446
2447 write!(w, "</div>").unwrap();
2448 }
2449