1 //! HTML formatting module
2 //!
3 //! This module contains a large number of `fmt::Display` implementations for
4 //! various types in `rustdoc::clean`.
5 //!
6 //! These implementations all emit HTML. As an internal implementation detail,
7 //! some of them support an alternate format that emits text, but that should
8 //! not be used external to this module.
9
10 use std::borrow::Cow;
11 use std::cell::Cell;
12 use std::fmt::{self, Write};
13 use std::iter::{self, once};
14
15 use rustc_ast as ast;
16 use rustc_attr::{ConstStability, StabilityLevel};
17 use rustc_data_structures::captures::Captures;
18 use rustc_data_structures::fx::FxHashSet;
19 use rustc_hir as hir;
20 use rustc_hir::def::DefKind;
21 use rustc_hir::def_id::DefId;
22 use rustc_metadata::creader::{CStore, LoadedMacro};
23 use rustc_middle::ty;
24 use rustc_middle::ty::TyCtxt;
25 use rustc_span::symbol::kw;
26 use rustc_span::{sym, Symbol};
27 use rustc_target::spec::abi::Abi;
28
29 use itertools::Itertools;
30
31 use crate::clean::{
32 self, types::ExternalLocation, utils::find_nearest_parent_module, ExternalCrate, ItemId,
33 PrimitiveType,
34 };
35 use crate::formats::item_type::ItemType;
36 use crate::html::escape::Escape;
37 use crate::html::render::Context;
38 use crate::passes::collect_intra_doc_links::UrlFragment;
39
40 use super::url_parts_builder::estimate_item_path_byte_length;
41 use super::url_parts_builder::UrlPartsBuilder;
42
43 pub(crate) trait Print {
print(self, buffer: &mut Buffer)44 fn print(self, buffer: &mut Buffer);
45 }
46
47 impl<F> Print for F
48 where
49 F: FnOnce(&mut Buffer),
50 {
print(self, buffer: &mut Buffer)51 fn print(self, buffer: &mut Buffer) {
52 (self)(buffer)
53 }
54 }
55
56 impl Print for String {
print(self, buffer: &mut Buffer)57 fn print(self, buffer: &mut Buffer) {
58 buffer.write_str(&self);
59 }
60 }
61
62 impl Print for &'_ str {
print(self, buffer: &mut Buffer)63 fn print(self, buffer: &mut Buffer) {
64 buffer.write_str(self);
65 }
66 }
67
68 #[derive(Debug, Clone)]
69 pub(crate) struct Buffer {
70 for_html: bool,
71 buffer: String,
72 }
73
74 impl core::fmt::Write for Buffer {
75 #[inline]
write_str(&mut self, s: &str) -> fmt::Result76 fn write_str(&mut self, s: &str) -> fmt::Result {
77 self.buffer.write_str(s)
78 }
79
80 #[inline]
write_char(&mut self, c: char) -> fmt::Result81 fn write_char(&mut self, c: char) -> fmt::Result {
82 self.buffer.write_char(c)
83 }
84
85 #[inline]
write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result86 fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
87 self.buffer.write_fmt(args)
88 }
89 }
90
91 impl Buffer {
empty_from(v: &Buffer) -> Buffer92 pub(crate) fn empty_from(v: &Buffer) -> Buffer {
93 Buffer { for_html: v.for_html, buffer: String::new() }
94 }
95
html() -> Buffer96 pub(crate) fn html() -> Buffer {
97 Buffer { for_html: true, buffer: String::new() }
98 }
99
new() -> Buffer100 pub(crate) fn new() -> Buffer {
101 Buffer { for_html: false, buffer: String::new() }
102 }
103
is_empty(&self) -> bool104 pub(crate) fn is_empty(&self) -> bool {
105 self.buffer.is_empty()
106 }
107
into_inner(self) -> String108 pub(crate) fn into_inner(self) -> String {
109 self.buffer
110 }
111
push_str(&mut self, s: &str)112 pub(crate) fn push_str(&mut self, s: &str) {
113 self.buffer.push_str(s);
114 }
115
push_buffer(&mut self, other: Buffer)116 pub(crate) fn push_buffer(&mut self, other: Buffer) {
117 self.buffer.push_str(&other.buffer);
118 }
119
120 // Intended for consumption by write! and writeln! (std::fmt) but without
121 // the fmt::Result return type imposed by fmt::Write (and avoiding the trait
122 // import).
write_str(&mut self, s: &str)123 pub(crate) fn write_str(&mut self, s: &str) {
124 self.buffer.push_str(s);
125 }
126
127 // Intended for consumption by write! and writeln! (std::fmt) but without
128 // the fmt::Result return type imposed by fmt::Write (and avoiding the trait
129 // import).
write_fmt(&mut self, v: fmt::Arguments<'_>)130 pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) {
131 self.buffer.write_fmt(v).unwrap();
132 }
133
to_display<T: Print>(mut self, t: T) -> String134 pub(crate) fn to_display<T: Print>(mut self, t: T) -> String {
135 t.print(&mut self);
136 self.into_inner()
137 }
138
reserve(&mut self, additional: usize)139 pub(crate) fn reserve(&mut self, additional: usize) {
140 self.buffer.reserve(additional)
141 }
142
len(&self) -> usize143 pub(crate) fn len(&self) -> usize {
144 self.buffer.len()
145 }
146 }
147
comma_sep<T: fmt::Display>( items: impl Iterator<Item = T>, space_after_comma: bool, ) -> impl fmt::Display148 pub(crate) fn comma_sep<T: fmt::Display>(
149 items: impl Iterator<Item = T>,
150 space_after_comma: bool,
151 ) -> impl fmt::Display {
152 display_fn(move |f| {
153 for (i, item) in items.enumerate() {
154 if i != 0 {
155 write!(f, ",{}", if space_after_comma { " " } else { "" })?;
156 }
157 fmt::Display::fmt(&item, f)?;
158 }
159 Ok(())
160 })
161 }
162
print_generic_bounds<'a, 'tcx: 'a>( bounds: &'a [clean::GenericBound], cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>163 pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>(
164 bounds: &'a [clean::GenericBound],
165 cx: &'a Context<'tcx>,
166 ) -> impl fmt::Display + 'a + Captures<'tcx> {
167 display_fn(move |f| {
168 let mut bounds_dup = FxHashSet::default();
169
170 for (i, bound) in bounds.iter().filter(|b| bounds_dup.insert(*b)).enumerate() {
171 if i > 0 {
172 f.write_str(" + ")?;
173 }
174 fmt::Display::fmt(&bound.print(cx), f)?;
175 }
176 Ok(())
177 })
178 }
179
180 impl clean::GenericParamDef {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>181 pub(crate) fn print<'a, 'tcx: 'a>(
182 &'a self,
183 cx: &'a Context<'tcx>,
184 ) -> impl fmt::Display + 'a + Captures<'tcx> {
185 display_fn(move |f| match &self.kind {
186 clean::GenericParamDefKind::Lifetime { outlives } => {
187 write!(f, "{}", self.name)?;
188
189 if !outlives.is_empty() {
190 f.write_str(": ")?;
191 for (i, lt) in outlives.iter().enumerate() {
192 if i != 0 {
193 f.write_str(" + ")?;
194 }
195 write!(f, "{}", lt.print())?;
196 }
197 }
198
199 Ok(())
200 }
201 clean::GenericParamDefKind::Type { bounds, default, .. } => {
202 f.write_str(self.name.as_str())?;
203
204 if !bounds.is_empty() {
205 if f.alternate() {
206 write!(f, ": {:#}", print_generic_bounds(bounds, cx))?;
207 } else {
208 write!(f, ": {}", print_generic_bounds(bounds, cx))?;
209 }
210 }
211
212 if let Some(ref ty) = default {
213 if f.alternate() {
214 write!(f, " = {:#}", ty.print(cx))?;
215 } else {
216 write!(f, " = {}", ty.print(cx))?;
217 }
218 }
219
220 Ok(())
221 }
222 clean::GenericParamDefKind::Const { ty, default, .. } => {
223 if f.alternate() {
224 write!(f, "const {}: {:#}", self.name, ty.print(cx))?;
225 } else {
226 write!(f, "const {}: {}", self.name, ty.print(cx))?;
227 }
228
229 if let Some(default) = default {
230 if f.alternate() {
231 write!(f, " = {:#}", default)?;
232 } else {
233 write!(f, " = {}", default)?;
234 }
235 }
236
237 Ok(())
238 }
239 })
240 }
241 }
242
243 impl clean::Generics {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>244 pub(crate) fn print<'a, 'tcx: 'a>(
245 &'a self,
246 cx: &'a Context<'tcx>,
247 ) -> impl fmt::Display + 'a + Captures<'tcx> {
248 display_fn(move |f| {
249 let mut real_params =
250 self.params.iter().filter(|p| !p.is_synthetic_type_param()).peekable();
251 if real_params.peek().is_none() {
252 return Ok(());
253 }
254
255 if f.alternate() {
256 write!(f, "<{:#}>", comma_sep(real_params.map(|g| g.print(cx)), true))
257 } else {
258 write!(f, "<{}>", comma_sep(real_params.map(|g| g.print(cx)), true))
259 }
260 })
261 }
262 }
263
264 #[derive(Clone, Copy, PartialEq, Eq)]
265 pub(crate) enum Ending {
266 Newline,
267 NoNewline,
268 }
269
270 /// * The Generics from which to emit a where-clause.
271 /// * The number of spaces to indent each line with.
272 /// * Whether the where-clause needs to add a comma and newline after the last bound.
print_where_clause<'a, 'tcx: 'a>( gens: &'a clean::Generics, cx: &'a Context<'tcx>, indent: usize, ending: Ending, ) -> impl fmt::Display + 'a + Captures<'tcx>273 pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
274 gens: &'a clean::Generics,
275 cx: &'a Context<'tcx>,
276 indent: usize,
277 ending: Ending,
278 ) -> impl fmt::Display + 'a + Captures<'tcx> {
279 display_fn(move |f| {
280 let mut where_predicates = gens.where_predicates.iter().filter(|pred| {
281 !matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty())
282 }).map(|pred| {
283 display_fn(move |f| {
284 if f.alternate() {
285 f.write_str(" ")?;
286 } else {
287 f.write_str("\n")?;
288 }
289
290 match pred {
291 clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
292 let ty_cx = ty.print(cx);
293 let generic_bounds = print_generic_bounds(bounds, cx);
294
295 if bound_params.is_empty() {
296 if f.alternate() {
297 write!(f, "{ty_cx:#}: {generic_bounds:#}")
298 } else {
299 write!(f, "{ty_cx}: {generic_bounds}")
300 }
301 } else {
302 if f.alternate() {
303 write!(
304 f,
305 "for<{:#}> {ty_cx:#}: {generic_bounds:#}",
306 comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
307 )
308 } else {
309 write!(
310 f,
311 "for<{}> {ty_cx}: {generic_bounds}",
312 comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
313 )
314 }
315 }
316 }
317 clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
318 let mut bounds_display = String::new();
319 for bound in bounds.iter().map(|b| b.print(cx)) {
320 write!(bounds_display, "{bound} + ")?;
321 }
322 bounds_display.truncate(bounds_display.len() - " + ".len());
323 write!(f, "{}: {bounds_display}", lifetime.print())
324 }
325 // FIXME(fmease): Render bound params.
326 clean::WherePredicate::EqPredicate { lhs, rhs, bound_params: _ } => {
327 if f.alternate() {
328 write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
329 } else {
330 write!(f, "{} == {}", lhs.print(cx), rhs.print(cx))
331 }
332 }
333 }
334 })
335 }).peekable();
336
337 if where_predicates.peek().is_none() {
338 return Ok(());
339 }
340
341 let where_preds = comma_sep(where_predicates, false);
342 let clause = if f.alternate() {
343 if ending == Ending::Newline {
344 format!(" where{where_preds},")
345 } else {
346 format!(" where{where_preds}")
347 }
348 } else {
349 let mut br_with_padding = String::with_capacity(6 * indent + 28);
350 br_with_padding.push('\n');
351
352 let where_indent = 3;
353 let padding_amount = if ending == Ending::Newline {
354 indent + 4
355 } else if indent == 0 {
356 4
357 } else {
358 indent + where_indent + "where ".len()
359 };
360
361 for _ in 0..padding_amount {
362 br_with_padding.push(' ');
363 }
364 let where_preds = where_preds.to_string().replace('\n', &br_with_padding);
365
366 if ending == Ending::Newline {
367 let mut clause = " ".repeat(indent.saturating_sub(1));
368 write!(clause, "<span class=\"where fmt-newline\">where{where_preds},</span>")?;
369 clause
370 } else {
371 // insert a newline after a single space but before multiple spaces at the start
372 if indent == 0 {
373 format!("\n<span class=\"where\">where{where_preds}</span>")
374 } else {
375 // put the first one on the same line as the 'where' keyword
376 let where_preds = where_preds.replacen(&br_with_padding, " ", 1);
377
378 let mut clause = br_with_padding;
379 // +1 is for `\n`.
380 clause.truncate(indent + 1 + where_indent);
381
382 write!(clause, "<span class=\"where\">where{where_preds}</span>")?;
383 clause
384 }
385 }
386 };
387 write!(f, "{clause}")
388 })
389 }
390
391 impl clean::Lifetime {
print(&self) -> impl fmt::Display + '_392 pub(crate) fn print(&self) -> impl fmt::Display + '_ {
393 self.0.as_str()
394 }
395 }
396
397 impl clean::Constant {
print(&self, tcx: TyCtxt<'_>) -> impl fmt::Display + '_398 pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl fmt::Display + '_ {
399 let expr = self.expr(tcx);
400 display_fn(
401 move |f| {
402 if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) }
403 },
404 )
405 }
406 }
407
408 impl clean::PolyTrait {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>409 fn print<'a, 'tcx: 'a>(
410 &'a self,
411 cx: &'a Context<'tcx>,
412 ) -> impl fmt::Display + 'a + Captures<'tcx> {
413 display_fn(move |f| {
414 if !self.generic_params.is_empty() {
415 if f.alternate() {
416 write!(
417 f,
418 "for<{:#}> ",
419 comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true)
420 )?;
421 } else {
422 write!(
423 f,
424 "for<{}> ",
425 comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true)
426 )?;
427 }
428 }
429 if f.alternate() {
430 write!(f, "{:#}", self.trait_.print(cx))
431 } else {
432 write!(f, "{}", self.trait_.print(cx))
433 }
434 })
435 }
436 }
437
438 impl clean::GenericBound {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>439 pub(crate) fn print<'a, 'tcx: 'a>(
440 &'a self,
441 cx: &'a Context<'tcx>,
442 ) -> impl fmt::Display + 'a + Captures<'tcx> {
443 display_fn(move |f| match self {
444 clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()),
445 clean::GenericBound::TraitBound(ty, modifier) => {
446 let modifier_str = match modifier {
447 hir::TraitBoundModifier::None => "",
448 hir::TraitBoundModifier::Maybe => "?",
449 hir::TraitBoundModifier::Negative => "!",
450 // ~const is experimental; do not display those bounds in rustdoc
451 hir::TraitBoundModifier::MaybeConst => "",
452 };
453 if f.alternate() {
454 write!(f, "{}{:#}", modifier_str, ty.print(cx))
455 } else {
456 write!(f, "{}{}", modifier_str, ty.print(cx))
457 }
458 }
459 })
460 }
461 }
462
463 impl clean::GenericArgs {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>464 fn print<'a, 'tcx: 'a>(
465 &'a self,
466 cx: &'a Context<'tcx>,
467 ) -> impl fmt::Display + 'a + Captures<'tcx> {
468 display_fn(move |f| {
469 match self {
470 clean::GenericArgs::AngleBracketed { args, bindings } => {
471 if !args.is_empty() || !bindings.is_empty() {
472 if f.alternate() {
473 f.write_str("<")?;
474 } else {
475 f.write_str("<")?;
476 }
477 let mut comma = false;
478 for arg in args.iter() {
479 if comma {
480 f.write_str(", ")?;
481 }
482 comma = true;
483 if f.alternate() {
484 write!(f, "{:#}", arg.print(cx))?;
485 } else {
486 write!(f, "{}", arg.print(cx))?;
487 }
488 }
489 for binding in bindings.iter() {
490 if comma {
491 f.write_str(", ")?;
492 }
493 comma = true;
494 if f.alternate() {
495 write!(f, "{:#}", binding.print(cx))?;
496 } else {
497 write!(f, "{}", binding.print(cx))?;
498 }
499 }
500 if f.alternate() {
501 f.write_str(">")?;
502 } else {
503 f.write_str(">")?;
504 }
505 }
506 }
507 clean::GenericArgs::Parenthesized { inputs, output } => {
508 f.write_str("(")?;
509 let mut comma = false;
510 for ty in inputs.iter() {
511 if comma {
512 f.write_str(", ")?;
513 }
514 comma = true;
515 if f.alternate() {
516 write!(f, "{:#}", ty.print(cx))?;
517 } else {
518 write!(f, "{}", ty.print(cx))?;
519 }
520 }
521 f.write_str(")")?;
522 if let Some(ref ty) = *output {
523 if f.alternate() {
524 write!(f, " -> {:#}", ty.print(cx))?;
525 } else {
526 write!(f, " -> {}", ty.print(cx))?;
527 }
528 }
529 }
530 }
531 Ok(())
532 })
533 }
534 }
535
536 // Possible errors when computing href link source for a `DefId`
537 #[derive(PartialEq, Eq)]
538 pub(crate) enum HrefError {
539 /// This item is known to rustdoc, but from a crate that does not have documentation generated.
540 ///
541 /// This can only happen for non-local items.
542 ///
543 /// # Example
544 ///
545 /// Crate `a` defines a public trait and crate `b` – the target crate that depends on `a` –
546 /// implements it for a local type.
547 /// We document `b` but **not** `a` (we only _build_ the latter – with `rustc`):
548 ///
549 /// ```sh
550 /// rustc a.rs --crate-type=lib
551 /// rustdoc b.rs --crate-type=lib --extern=a=liba.rlib
552 /// ```
553 ///
554 /// Now, the associated items in the trait impl want to link to the corresponding item in the
555 /// trait declaration (see `html::render::assoc_href_attr`) but it's not available since their
556 /// *documentation (was) not built*.
557 DocumentationNotBuilt,
558 /// This can only happen for non-local items when `--document-private-items` is not passed.
559 Private,
560 // Not in external cache, href link should be in same page
561 NotInExternalCache,
562 }
563
564 // Panics if `syms` is empty.
join_with_double_colon(syms: &[Symbol]) -> String565 pub(crate) fn join_with_double_colon(syms: &[Symbol]) -> String {
566 let mut s = String::with_capacity(estimate_item_path_byte_length(syms.len()));
567 s.push_str(syms[0].as_str());
568 for sym in &syms[1..] {
569 s.push_str("::");
570 s.push_str(sym.as_str());
571 }
572 s
573 }
574
575 /// This function is to get the external macro path because they are not in the cache used in
576 /// `href_with_root_path`.
generate_macro_def_id_path( def_id: DefId, cx: &Context<'_>, root_path: Option<&str>, ) -> Result<(String, ItemType, Vec<Symbol>), HrefError>577 fn generate_macro_def_id_path(
578 def_id: DefId,
579 cx: &Context<'_>,
580 root_path: Option<&str>,
581 ) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
582 let tcx = cx.shared.tcx;
583 let crate_name = tcx.crate_name(def_id.krate);
584 let cache = cx.cache();
585
586 let fqp: Vec<Symbol> = tcx
587 .def_path(def_id)
588 .data
589 .into_iter()
590 .filter_map(|elem| {
591 // extern blocks (and a few others things) have an empty name.
592 match elem.data.get_opt_name() {
593 Some(s) if !s.is_empty() => Some(s),
594 _ => None,
595 }
596 })
597 .collect();
598 let mut relative = fqp.iter().copied();
599 let cstore = CStore::from_tcx(tcx);
600 // We need this to prevent a `panic` when this function is used from intra doc links...
601 if !cstore.has_crate_data(def_id.krate) {
602 debug!("No data for crate {}", crate_name);
603 return Err(HrefError::NotInExternalCache);
604 }
605 // Check to see if it is a macro 2.0 or built-in macro.
606 // More information in <https://rust-lang.github.io/rfcs/1584-macros.html>.
607 let is_macro_2 = match cstore.load_macro_untracked(def_id, tcx.sess) {
608 LoadedMacro::MacroDef(def, _) => {
609 // If `ast_def.macro_rules` is `true`, then it's not a macro 2.0.
610 matches!(&def.kind, ast::ItemKind::MacroDef(ast_def) if !ast_def.macro_rules)
611 }
612 _ => false,
613 };
614
615 let mut path = if is_macro_2 {
616 once(crate_name).chain(relative).collect()
617 } else {
618 vec![crate_name, relative.next_back().unwrap()]
619 };
620 if path.len() < 2 {
621 // The minimum we can have is the crate name followed by the macro name. If shorter, then
622 // it means that `relative` was empty, which is an error.
623 debug!("macro path cannot be empty!");
624 return Err(HrefError::NotInExternalCache);
625 }
626
627 if let Some(last) = path.last_mut() {
628 *last = Symbol::intern(&format!("macro.{}.html", last.as_str()));
629 }
630
631 let url = match cache.extern_locations[&def_id.krate] {
632 ExternalLocation::Remote(ref s) => {
633 // `ExternalLocation::Remote` always end with a `/`.
634 format!("{}{}", s, path.iter().map(|p| p.as_str()).join("/"))
635 }
636 ExternalLocation::Local => {
637 // `root_path` always end with a `/`.
638 format!(
639 "{}{}/{}",
640 root_path.unwrap_or(""),
641 crate_name,
642 path.iter().map(|p| p.as_str()).join("/")
643 )
644 }
645 ExternalLocation::Unknown => {
646 debug!("crate {} not in cache when linkifying macros", crate_name);
647 return Err(HrefError::NotInExternalCache);
648 }
649 };
650 Ok((url, ItemType::Macro, fqp))
651 }
652
href_with_root_path( did: DefId, cx: &Context<'_>, root_path: Option<&str>, ) -> Result<(String, ItemType, Vec<Symbol>), HrefError>653 pub(crate) fn href_with_root_path(
654 did: DefId,
655 cx: &Context<'_>,
656 root_path: Option<&str>,
657 ) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
658 let tcx = cx.tcx();
659 let def_kind = tcx.def_kind(did);
660 let did = match def_kind {
661 DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
662 // documented on their parent's page
663 tcx.parent(did)
664 }
665 _ => did,
666 };
667 let cache = cx.cache();
668 let relative_to = &cx.current;
669 fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
670 if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
671 }
672
673 if !did.is_local()
674 && !cache.effective_visibilities.is_directly_public(tcx, did)
675 && !cache.document_private
676 && !cache.primitive_locations.values().any(|&id| id == did)
677 {
678 return Err(HrefError::Private);
679 }
680
681 let mut is_remote = false;
682 let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
683 Some(&(ref fqp, shortty)) => (fqp, shortty, {
684 let module_fqp = to_module_fqp(shortty, fqp.as_slice());
685 debug!(?fqp, ?shortty, ?module_fqp);
686 href_relative_parts(module_fqp, relative_to).collect()
687 }),
688 None => {
689 if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did) {
690 let module_fqp = to_module_fqp(shortty, fqp);
691 (
692 fqp,
693 shortty,
694 match cache.extern_locations[&did.krate] {
695 ExternalLocation::Remote(ref s) => {
696 is_remote = true;
697 let s = s.trim_end_matches('/');
698 let mut builder = UrlPartsBuilder::singleton(s);
699 builder.extend(module_fqp.iter().copied());
700 builder
701 }
702 ExternalLocation::Local => {
703 href_relative_parts(module_fqp, relative_to).collect()
704 }
705 ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
706 },
707 )
708 } else if matches!(def_kind, DefKind::Macro(_)) {
709 return generate_macro_def_id_path(did, cx, root_path);
710 } else {
711 return Err(HrefError::NotInExternalCache);
712 }
713 }
714 };
715 if !is_remote && let Some(root_path) = root_path {
716 let root = root_path.trim_end_matches('/');
717 url_parts.push_front(root);
718 }
719 debug!(?url_parts);
720 match shortty {
721 ItemType::Module => {
722 url_parts.push("index.html");
723 }
724 _ => {
725 let prefix = shortty.as_str();
726 let last = fqp.last().unwrap();
727 url_parts.push_fmt(format_args!("{}.{}.html", prefix, last));
728 }
729 }
730 Ok((url_parts.finish(), shortty, fqp.to_vec()))
731 }
732
href( did: DefId, cx: &Context<'_>, ) -> Result<(String, ItemType, Vec<Symbol>), HrefError>733 pub(crate) fn href(
734 did: DefId,
735 cx: &Context<'_>,
736 ) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
737 href_with_root_path(did, cx, None)
738 }
739
740 /// Both paths should only be modules.
741 /// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
742 /// both need `../iter/trait.Iterator.html` to get at the iterator trait.
href_relative_parts<'fqp>( fqp: &'fqp [Symbol], relative_to_fqp: &[Symbol], ) -> Box<dyn Iterator<Item = Symbol> + 'fqp>743 pub(crate) fn href_relative_parts<'fqp>(
744 fqp: &'fqp [Symbol],
745 relative_to_fqp: &[Symbol],
746 ) -> Box<dyn Iterator<Item = Symbol> + 'fqp> {
747 for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
748 // e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
749 if f != r {
750 let dissimilar_part_count = relative_to_fqp.len() - i;
751 let fqp_module = &fqp[i..fqp.len()];
752 return Box::new(
753 iter::repeat(sym::dotdot)
754 .take(dissimilar_part_count)
755 .chain(fqp_module.iter().copied()),
756 );
757 }
758 }
759 // e.g. linking to std::sync::atomic from std::sync
760 if relative_to_fqp.len() < fqp.len() {
761 Box::new(fqp[relative_to_fqp.len()..fqp.len()].iter().copied())
762 // e.g. linking to std::sync from std::sync::atomic
763 } else if fqp.len() < relative_to_fqp.len() {
764 let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
765 Box::new(iter::repeat(sym::dotdot).take(dissimilar_part_count))
766 // linking to the same module
767 } else {
768 Box::new(iter::empty())
769 }
770 }
771
link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Context<'_>) -> String772 pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Context<'_>) -> String {
773 let cache = cx.cache();
774 let Some((fqp, shortty)) = cache.paths.get(&did)
775 .or_else(|| cache.external_paths.get(&did))
776 else { return String::new() };
777 let mut buf = Buffer::new();
778 let fqp = if *shortty == ItemType::Primitive {
779 // primitives are documented in a crate, but not actually part of it
780 &fqp[fqp.len() - 1..]
781 } else {
782 &fqp
783 };
784 if let &Some(UrlFragment::Item(id)) = fragment {
785 write!(buf, "{} ", cx.tcx().def_descr(id));
786 for component in fqp {
787 write!(buf, "{component}::");
788 }
789 write!(buf, "{}", cx.tcx().item_name(id));
790 } else if !fqp.is_empty() {
791 let mut fqp_it = fqp.into_iter();
792 write!(buf, "{shortty} {}", fqp_it.next().unwrap());
793 for component in fqp_it {
794 write!(buf, "::{component}");
795 }
796 }
797 buf.into_inner()
798 }
799
800 /// Used to render a [`clean::Path`].
resolved_path<'cx>( w: &mut fmt::Formatter<'_>, did: DefId, path: &clean::Path, print_all: bool, use_absolute: bool, cx: &'cx Context<'_>, ) -> fmt::Result801 fn resolved_path<'cx>(
802 w: &mut fmt::Formatter<'_>,
803 did: DefId,
804 path: &clean::Path,
805 print_all: bool,
806 use_absolute: bool,
807 cx: &'cx Context<'_>,
808 ) -> fmt::Result {
809 let last = path.segments.last().unwrap();
810
811 if print_all {
812 for seg in &path.segments[..path.segments.len() - 1] {
813 write!(w, "{}::", if seg.name == kw::PathRoot { "" } else { seg.name.as_str() })?;
814 }
815 }
816 if w.alternate() {
817 write!(w, "{}{:#}", &last.name, last.args.print(cx))?;
818 } else {
819 let path = if use_absolute {
820 if let Ok((_, _, fqp)) = href(did, cx) {
821 format!(
822 "{}::{}",
823 join_with_double_colon(&fqp[..fqp.len() - 1]),
824 anchor(did, *fqp.last().unwrap(), cx)
825 )
826 } else {
827 last.name.to_string()
828 }
829 } else {
830 anchor(did, last.name, cx).to_string()
831 };
832 write!(w, "{}{}", path, last.args.print(cx))?;
833 }
834 Ok(())
835 }
836
primitive_link( f: &mut fmt::Formatter<'_>, prim: clean::PrimitiveType, name: &str, cx: &Context<'_>, ) -> fmt::Result837 fn primitive_link(
838 f: &mut fmt::Formatter<'_>,
839 prim: clean::PrimitiveType,
840 name: &str,
841 cx: &Context<'_>,
842 ) -> fmt::Result {
843 primitive_link_fragment(f, prim, name, "", cx)
844 }
845
primitive_link_fragment( f: &mut fmt::Formatter<'_>, prim: clean::PrimitiveType, name: &str, fragment: &str, cx: &Context<'_>, ) -> fmt::Result846 fn primitive_link_fragment(
847 f: &mut fmt::Formatter<'_>,
848 prim: clean::PrimitiveType,
849 name: &str,
850 fragment: &str,
851 cx: &Context<'_>,
852 ) -> fmt::Result {
853 let m = &cx.cache();
854 let mut needs_termination = false;
855 if !f.alternate() {
856 match m.primitive_locations.get(&prim) {
857 Some(&def_id) if def_id.is_local() => {
858 let len = cx.current.len();
859 let len = if len == 0 { 0 } else { len - 1 };
860 write!(
861 f,
862 "<a class=\"primitive\" href=\"{}primitive.{}.html{fragment}\">",
863 "../".repeat(len),
864 prim.as_sym()
865 )?;
866 needs_termination = true;
867 }
868 Some(&def_id) => {
869 let loc = match m.extern_locations[&def_id.krate] {
870 ExternalLocation::Remote(ref s) => {
871 let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
872 let builder: UrlPartsBuilder =
873 [s.as_str().trim_end_matches('/'), cname_sym.as_str()]
874 .into_iter()
875 .collect();
876 Some(builder)
877 }
878 ExternalLocation::Local => {
879 let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
880 Some(if cx.current.first() == Some(&cname_sym) {
881 iter::repeat(sym::dotdot).take(cx.current.len() - 1).collect()
882 } else {
883 iter::repeat(sym::dotdot)
884 .take(cx.current.len())
885 .chain(iter::once(cname_sym))
886 .collect()
887 })
888 }
889 ExternalLocation::Unknown => None,
890 };
891 if let Some(mut loc) = loc {
892 loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
893 write!(f, "<a class=\"primitive\" href=\"{}{fragment}\">", loc.finish())?;
894 needs_termination = true;
895 }
896 }
897 None => {}
898 }
899 }
900 write!(f, "{}", name)?;
901 if needs_termination {
902 write!(f, "</a>")?;
903 }
904 Ok(())
905 }
906
907 /// Helper to render type parameters
tybounds<'a, 'tcx: 'a>( bounds: &'a [clean::PolyTrait], lt: &'a Option<clean::Lifetime>, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>908 fn tybounds<'a, 'tcx: 'a>(
909 bounds: &'a [clean::PolyTrait],
910 lt: &'a Option<clean::Lifetime>,
911 cx: &'a Context<'tcx>,
912 ) -> impl fmt::Display + 'a + Captures<'tcx> {
913 display_fn(move |f| {
914 for (i, bound) in bounds.iter().enumerate() {
915 if i > 0 {
916 write!(f, " + ")?;
917 }
918
919 fmt::Display::fmt(&bound.print(cx), f)?;
920 }
921
922 if let Some(lt) = lt {
923 write!(f, " + ")?;
924 fmt::Display::fmt(<.print(), f)?;
925 }
926 Ok(())
927 })
928 }
929
anchor<'a, 'cx: 'a>( did: DefId, text: Symbol, cx: &'cx Context<'_>, ) -> impl fmt::Display + 'a930 pub(crate) fn anchor<'a, 'cx: 'a>(
931 did: DefId,
932 text: Symbol,
933 cx: &'cx Context<'_>,
934 ) -> impl fmt::Display + 'a {
935 let parts = href(did, cx);
936 display_fn(move |f| {
937 if let Ok((url, short_ty, fqp)) = parts {
938 write!(
939 f,
940 r#"<a class="{}" href="{}" title="{} {}">{}</a>"#,
941 short_ty,
942 url,
943 short_ty,
944 join_with_double_colon(&fqp),
945 text.as_str()
946 )
947 } else {
948 write!(f, "{}", text)
949 }
950 })
951 }
952
fmt_type<'cx>( t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool, cx: &'cx Context<'_>, ) -> fmt::Result953 fn fmt_type<'cx>(
954 t: &clean::Type,
955 f: &mut fmt::Formatter<'_>,
956 use_absolute: bool,
957 cx: &'cx Context<'_>,
958 ) -> fmt::Result {
959 trace!("fmt_type(t = {:?})", t);
960
961 match *t {
962 clean::Generic(name) => write!(f, "{}", name),
963 clean::Type::Path { ref path } => {
964 // Paths like `T::Output` and `Self::Output` should be rendered with all segments.
965 let did = path.def_id();
966 resolved_path(f, did, path, path.is_assoc_ty(), use_absolute, cx)
967 }
968 clean::DynTrait(ref bounds, ref lt) => {
969 f.write_str("dyn ")?;
970 fmt::Display::fmt(&tybounds(bounds, lt, cx), f)
971 }
972 clean::Infer => write!(f, "_"),
973 clean::Primitive(clean::PrimitiveType::Never) => {
974 primitive_link(f, PrimitiveType::Never, "!", cx)
975 }
976 clean::Primitive(prim) => primitive_link(f, prim, prim.as_sym().as_str(), cx),
977 clean::BareFunction(ref decl) => {
978 if f.alternate() {
979 write!(
980 f,
981 "{:#}{}{:#}fn{:#}",
982 decl.print_hrtb_with_space(cx),
983 decl.unsafety.print_with_space(),
984 print_abi_with_space(decl.abi),
985 decl.decl.print(cx),
986 )
987 } else {
988 write!(
989 f,
990 "{}{}{}",
991 decl.print_hrtb_with_space(cx),
992 decl.unsafety.print_with_space(),
993 print_abi_with_space(decl.abi)
994 )?;
995 primitive_link(f, PrimitiveType::Fn, "fn", cx)?;
996 write!(f, "{}", decl.decl.print(cx))
997 }
998 }
999 clean::Tuple(ref typs) => {
1000 match &typs[..] {
1001 &[] => primitive_link(f, PrimitiveType::Unit, "()", cx),
1002 [one] => {
1003 if let clean::Generic(name) = one {
1004 primitive_link(f, PrimitiveType::Tuple, &format!("({name},)"), cx)
1005 } else {
1006 write!(f, "(")?;
1007 // Carry `f.alternate()` into this display w/o branching manually.
1008 fmt::Display::fmt(&one.print(cx), f)?;
1009 write!(f, ",)")
1010 }
1011 }
1012 many => {
1013 let generic_names: Vec<Symbol> = many
1014 .iter()
1015 .filter_map(|t| match t {
1016 clean::Generic(name) => Some(*name),
1017 _ => None,
1018 })
1019 .collect();
1020 let is_generic = generic_names.len() == many.len();
1021 if is_generic {
1022 primitive_link(
1023 f,
1024 PrimitiveType::Tuple,
1025 &format!("({})", generic_names.iter().map(|s| s.as_str()).join(", ")),
1026 cx,
1027 )
1028 } else {
1029 write!(f, "(")?;
1030 for (i, item) in many.iter().enumerate() {
1031 if i != 0 {
1032 write!(f, ", ")?;
1033 }
1034 // Carry `f.alternate()` into this display w/o branching manually.
1035 fmt::Display::fmt(&item.print(cx), f)?;
1036 }
1037 write!(f, ")")
1038 }
1039 }
1040 }
1041 }
1042 clean::Slice(ref t) => match **t {
1043 clean::Generic(name) => {
1044 primitive_link(f, PrimitiveType::Slice, &format!("[{name}]"), cx)
1045 }
1046 _ => {
1047 write!(f, "[")?;
1048 fmt::Display::fmt(&t.print(cx), f)?;
1049 write!(f, "]")
1050 }
1051 },
1052 clean::Array(ref t, ref n) => match **t {
1053 clean::Generic(name) if !f.alternate() => primitive_link(
1054 f,
1055 PrimitiveType::Array,
1056 &format!("[{name}; {n}]", n = Escape(n)),
1057 cx,
1058 ),
1059 _ => {
1060 write!(f, "[")?;
1061 fmt::Display::fmt(&t.print(cx), f)?;
1062 if f.alternate() {
1063 write!(f, "; {n}")?;
1064 } else {
1065 write!(f, "; ")?;
1066 primitive_link(f, PrimitiveType::Array, &format!("{n}", n = Escape(n)), cx)?;
1067 }
1068 write!(f, "]")
1069 }
1070 },
1071 clean::RawPointer(m, ref t) => {
1072 let m = match m {
1073 hir::Mutability::Mut => "mut",
1074 hir::Mutability::Not => "const",
1075 };
1076
1077 if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() {
1078 let text = if f.alternate() {
1079 format!("*{} {:#}", m, t.print(cx))
1080 } else {
1081 format!("*{} {}", m, t.print(cx))
1082 };
1083 primitive_link(f, clean::PrimitiveType::RawPointer, &text, cx)
1084 } else {
1085 primitive_link(f, clean::PrimitiveType::RawPointer, &format!("*{} ", m), cx)?;
1086 fmt::Display::fmt(&t.print(cx), f)
1087 }
1088 }
1089 clean::BorrowedRef { lifetime: ref l, mutability, type_: ref ty } => {
1090 let lt = match l {
1091 Some(l) => format!("{} ", l.print()),
1092 _ => String::new(),
1093 };
1094 let m = mutability.print_with_space();
1095 let amp = if f.alternate() { "&" } else { "&" };
1096 match **ty {
1097 clean::DynTrait(ref bounds, ref trait_lt)
1098 if bounds.len() > 1 || trait_lt.is_some() =>
1099 {
1100 write!(f, "{}{}{}(", amp, lt, m)?;
1101 fmt_type(ty, f, use_absolute, cx)?;
1102 write!(f, ")")
1103 }
1104 clean::Generic(name) => {
1105 primitive_link(f, PrimitiveType::Reference, &format!("{amp}{lt}{m}{name}"), cx)
1106 }
1107 _ => {
1108 write!(f, "{}{}{}", amp, lt, m)?;
1109 fmt_type(ty, f, use_absolute, cx)
1110 }
1111 }
1112 }
1113 clean::ImplTrait(ref bounds) => {
1114 if f.alternate() {
1115 write!(f, "impl {:#}", print_generic_bounds(bounds, cx))
1116 } else {
1117 write!(f, "impl {}", print_generic_bounds(bounds, cx))
1118 }
1119 }
1120 clean::QPath(box clean::QPathData {
1121 ref assoc,
1122 ref self_type,
1123 ref trait_,
1124 should_show_cast,
1125 }) => {
1126 // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719),
1127 // we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`).
1128
1129 if f.alternate() {
1130 if let Some(trait_) = trait_ && should_show_cast {
1131 write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))?
1132 } else {
1133 write!(f, "{:#}::", self_type.print(cx))?
1134 }
1135 } else {
1136 if let Some(trait_) = trait_ && should_show_cast {
1137 write!(f, "<{} as {}>::", self_type.print(cx), trait_.print(cx))?
1138 } else {
1139 write!(f, "{}::", self_type.print(cx))?
1140 }
1141 };
1142 // It's pretty unsightly to look at `<A as B>::C` in output, and
1143 // we've got hyperlinking on our side, so try to avoid longer
1144 // notation as much as possible by making `C` a hyperlink to trait
1145 // `B` to disambiguate.
1146 //
1147 // FIXME: this is still a lossy conversion and there should probably
1148 // be a better way of representing this in general? Most of
1149 // the ugliness comes from inlining across crates where
1150 // everything comes in as a fully resolved QPath (hard to
1151 // look at).
1152 if !f.alternate() {
1153 // FIXME(inherent_associated_types): We always link to the very first associated
1154 // type (in respect to source order) that bears the given name (`assoc.name`) and that is
1155 // affiliated with the computed `DefId`. This is obviously incorrect when we have
1156 // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself
1157 // through here and map it to the corresponding HTML ID that was generated by
1158 // `render::Context::derive_id` when the impl blocks were rendered.
1159 // There is no such mapping unfortunately.
1160 // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking
1161 // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()`
1162 // considering privacy, `doc(hidden)`, etc.
1163 // I don't feel like that right now :cold_sweat:.
1164
1165 let parent_href = match trait_ {
1166 Some(trait_) => href(trait_.def_id(), cx).ok(),
1167 None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()),
1168 };
1169
1170 if let Some((url, _, path)) = parent_href {
1171 write!(
1172 f,
1173 "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
1174 title=\"type {path}::{name}\">{name}</a>",
1175 shortty = ItemType::AssocType,
1176 name = assoc.name,
1177 path = join_with_double_colon(&path),
1178 )
1179 } else {
1180 write!(f, "{}", assoc.name)
1181 }
1182 } else {
1183 write!(f, "{}", assoc.name)
1184 }?;
1185
1186 // Carry `f.alternate()` into this display w/o branching manually.
1187 fmt::Display::fmt(&assoc.args.print(cx), f)
1188 }
1189 }
1190 }
1191
1192 impl clean::Type {
print<'b, 'a: 'b, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'b + Captures<'tcx>1193 pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
1194 &'a self,
1195 cx: &'a Context<'tcx>,
1196 ) -> impl fmt::Display + 'b + Captures<'tcx> {
1197 display_fn(move |f| fmt_type(self, f, false, cx))
1198 }
1199 }
1200
1201 impl clean::Path {
print<'b, 'a: 'b, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'b + Captures<'tcx>1202 pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
1203 &'a self,
1204 cx: &'a Context<'tcx>,
1205 ) -> impl fmt::Display + 'b + Captures<'tcx> {
1206 display_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx))
1207 }
1208 }
1209
1210 impl clean::Impl {
print<'a, 'tcx: 'a>( &'a self, use_absolute: bool, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1211 pub(crate) fn print<'a, 'tcx: 'a>(
1212 &'a self,
1213 use_absolute: bool,
1214 cx: &'a Context<'tcx>,
1215 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1216 display_fn(move |f| {
1217 if f.alternate() {
1218 write!(f, "impl{:#} ", self.generics.print(cx))?;
1219 } else {
1220 write!(f, "impl{} ", self.generics.print(cx))?;
1221 }
1222
1223 if let Some(ref ty) = self.trait_ {
1224 match self.polarity {
1225 ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => {}
1226 ty::ImplPolarity::Negative => write!(f, "!")?,
1227 }
1228 fmt::Display::fmt(&ty.print(cx), f)?;
1229 write!(f, " for ")?;
1230 }
1231
1232 if let clean::Type::Tuple(types) = &self.for_ &&
1233 let [clean::Type::Generic(name)] = &types[..] &&
1234 (self.kind.is_fake_variadic() || self.kind.is_auto())
1235 {
1236 // Hardcoded anchor library/core/src/primitive_docs.rs
1237 // Link should match `# Trait implementations`
1238 primitive_link_fragment(f, PrimitiveType::Tuple, &format!("({name}₁, {name}₂, …, {name}ₙ)"), "#trait-implementations-1", cx)?;
1239 } else if let clean::BareFunction(bare_fn) = &self.for_ &&
1240 let [clean::Argument { type_: clean::Type::Generic(name), .. }] = &bare_fn.decl.inputs.values[..] &&
1241 (self.kind.is_fake_variadic() || self.kind.is_auto())
1242 {
1243 // Hardcoded anchor library/core/src/primitive_docs.rs
1244 // Link should match `# Trait implementations`
1245
1246 let hrtb = bare_fn.print_hrtb_with_space(cx);
1247 let unsafety = bare_fn.unsafety.print_with_space();
1248 let abi = print_abi_with_space(bare_fn.abi);
1249 if f.alternate() {
1250 write!(
1251 f,
1252 "{hrtb:#}{unsafety}{abi:#}",
1253 )?;
1254 } else {
1255 write!(
1256 f,
1257 "{hrtb}{unsafety}{abi}",
1258 )?;
1259 }
1260 let ellipsis = if bare_fn.decl.c_variadic {
1261 ", ..."
1262 } else {
1263 ""
1264 };
1265 primitive_link_fragment(f, PrimitiveType::Tuple, &format!("fn ({name}₁, {name}₂, …, {name}ₙ{ellipsis})"), "#trait-implementations-1", cx)?;
1266 // Write output.
1267 if !bare_fn.decl.output.is_unit() {
1268 write!(f, " -> ")?;
1269 fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?;
1270 }
1271 } else if let Some(ty) = self.kind.as_blanket_ty() {
1272 fmt_type(ty, f, use_absolute, cx)?;
1273 } else {
1274 fmt_type(&self.for_, f, use_absolute, cx)?;
1275 }
1276
1277 fmt::Display::fmt(&print_where_clause(&self.generics, cx, 0, Ending::Newline), f)?;
1278 Ok(())
1279 })
1280 }
1281 }
1282
1283 impl clean::Arguments {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1284 pub(crate) fn print<'a, 'tcx: 'a>(
1285 &'a self,
1286 cx: &'a Context<'tcx>,
1287 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1288 display_fn(move |f| {
1289 for (i, input) in self.values.iter().enumerate() {
1290 write!(f, "{}: ", input.name)?;
1291
1292 if f.alternate() {
1293 write!(f, "{:#}", input.type_.print(cx))?;
1294 } else {
1295 write!(f, "{}", input.type_.print(cx))?;
1296 }
1297 if i + 1 < self.values.len() {
1298 write!(f, ", ")?;
1299 }
1300 }
1301 Ok(())
1302 })
1303 }
1304 }
1305
1306 impl clean::BareFunctionDecl {
print_hrtb_with_space<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1307 fn print_hrtb_with_space<'a, 'tcx: 'a>(
1308 &'a self,
1309 cx: &'a Context<'tcx>,
1310 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1311 display_fn(move |f| {
1312 if !self.generic_params.is_empty() {
1313 write!(
1314 f,
1315 "for<{}> ",
1316 comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true)
1317 )
1318 } else {
1319 Ok(())
1320 }
1321 })
1322 }
1323 }
1324
1325 // Implements Write but only counts the bytes "written".
1326 struct WriteCounter(usize);
1327
1328 impl std::fmt::Write for WriteCounter {
write_str(&mut self, s: &str) -> fmt::Result1329 fn write_str(&mut self, s: &str) -> fmt::Result {
1330 self.0 += s.len();
1331 Ok(())
1332 }
1333 }
1334
1335 // Implements Display by emitting the given number of spaces.
1336 struct Indent(usize);
1337
1338 impl fmt::Display for Indent {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result1339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1340 (0..self.0).for_each(|_| {
1341 f.write_char(' ').unwrap();
1342 });
1343 Ok(())
1344 }
1345 }
1346
1347 impl clean::FnDecl {
print<'b, 'a: 'b, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'b + Captures<'tcx>1348 pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
1349 &'a self,
1350 cx: &'a Context<'tcx>,
1351 ) -> impl fmt::Display + 'b + Captures<'tcx> {
1352 display_fn(move |f| {
1353 let ellipsis = if self.c_variadic { ", ..." } else { "" };
1354 if f.alternate() {
1355 write!(
1356 f,
1357 "({args:#}{ellipsis}){arrow:#}",
1358 args = self.inputs.print(cx),
1359 ellipsis = ellipsis,
1360 arrow = self.print_output(cx)
1361 )
1362 } else {
1363 write!(
1364 f,
1365 "({args}{ellipsis}){arrow}",
1366 args = self.inputs.print(cx),
1367 ellipsis = ellipsis,
1368 arrow = self.print_output(cx)
1369 )
1370 }
1371 })
1372 }
1373
1374 /// * `header_len`: The length of the function header and name. In other words, the number of
1375 /// characters in the function declaration up to but not including the parentheses.
1376 /// This is expected to go into a `<pre>`/`code-header` block, so indentation and newlines
1377 /// are preserved.
1378 /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
1379 /// necessary.
full_print<'a, 'tcx: 'a>( &'a self, header_len: usize, indent: usize, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1380 pub(crate) fn full_print<'a, 'tcx: 'a>(
1381 &'a self,
1382 header_len: usize,
1383 indent: usize,
1384 cx: &'a Context<'tcx>,
1385 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1386 display_fn(move |f| {
1387 // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
1388 let mut counter = WriteCounter(0);
1389 write!(&mut counter, "{:#}", display_fn(|f| { self.inner_full_print(None, f, cx) }))
1390 .unwrap();
1391 // If the text form was over 80 characters wide, we will line-wrap our output.
1392 let line_wrapping_indent =
1393 if header_len + counter.0 > 80 { Some(indent) } else { None };
1394 // Generate the final output. This happens to accept `{:#}` formatting to get textual
1395 // output but in practice it is only formatted with `{}` to get HTML output.
1396 self.inner_full_print(line_wrapping_indent, f, cx)
1397 })
1398 }
1399
inner_full_print( &self, line_wrapping_indent: Option<usize>, f: &mut fmt::Formatter<'_>, cx: &Context<'_>, ) -> fmt::Result1400 fn inner_full_print(
1401 &self,
1402 // For None, the declaration will not be line-wrapped. For Some(n),
1403 // the declaration will be line-wrapped, with an indent of n spaces.
1404 line_wrapping_indent: Option<usize>,
1405 f: &mut fmt::Formatter<'_>,
1406 cx: &Context<'_>,
1407 ) -> fmt::Result {
1408 let amp = if f.alternate() { "&" } else { "&" };
1409
1410 write!(f, "(")?;
1411 if let Some(n) = line_wrapping_indent && !self.inputs.values.is_empty() {
1412 write!(f, "\n{}", Indent(n + 4))?;
1413 }
1414 for (i, input) in self.inputs.values.iter().enumerate() {
1415 if i > 0 {
1416 match line_wrapping_indent {
1417 None => write!(f, ", ")?,
1418 Some(n) => write!(f, ",\n{}", Indent(n + 4))?,
1419 };
1420 }
1421 if let Some(selfty) = input.to_self() {
1422 match selfty {
1423 clean::SelfValue => {
1424 write!(f, "self")?;
1425 }
1426 clean::SelfBorrowed(Some(ref lt), mtbl) => {
1427 write!(f, "{}{} {}self", amp, lt.print(), mtbl.print_with_space())?;
1428 }
1429 clean::SelfBorrowed(None, mtbl) => {
1430 write!(f, "{}{}self", amp, mtbl.print_with_space())?;
1431 }
1432 clean::SelfExplicit(ref typ) => {
1433 write!(f, "self: ")?;
1434 fmt::Display::fmt(&typ.print(cx), f)?;
1435 }
1436 }
1437 } else {
1438 if input.is_const {
1439 write!(f, "const ")?;
1440 }
1441 write!(f, "{}: ", input.name)?;
1442 fmt::Display::fmt(&input.type_.print(cx), f)?;
1443 }
1444 }
1445
1446 if self.c_variadic {
1447 match line_wrapping_indent {
1448 None => write!(f, ", ...")?,
1449 Some(n) => write!(f, "\n{}...", Indent(n + 4))?,
1450 };
1451 }
1452
1453 match line_wrapping_indent {
1454 None => write!(f, ")")?,
1455 Some(n) => write!(f, "\n{})", Indent(n))?,
1456 };
1457
1458 fmt::Display::fmt(&self.print_output(cx), f)?;
1459 Ok(())
1460 }
1461
print_output<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1462 fn print_output<'a, 'tcx: 'a>(
1463 &'a self,
1464 cx: &'a Context<'tcx>,
1465 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1466 display_fn(move |f| match &self.output {
1467 clean::Tuple(tys) if tys.is_empty() => Ok(()),
1468 ty if f.alternate() => {
1469 write!(f, " -> {:#}", ty.print(cx))
1470 }
1471 ty => write!(f, " -> {}", ty.print(cx)),
1472 })
1473 }
1474 }
1475
visibility_print_with_space<'a, 'tcx: 'a>( visibility: Option<ty::Visibility<DefId>>, item_did: ItemId, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1476 pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>(
1477 visibility: Option<ty::Visibility<DefId>>,
1478 item_did: ItemId,
1479 cx: &'a Context<'tcx>,
1480 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1481 use std::fmt::Write as _;
1482
1483 let to_print: Cow<'static, str> = match visibility {
1484 None => "".into(),
1485 Some(ty::Visibility::Public) => "pub ".into(),
1486 Some(ty::Visibility::Restricted(vis_did)) => {
1487 // FIXME(camelid): This may not work correctly if `item_did` is a module.
1488 // However, rustdoc currently never displays a module's
1489 // visibility, so it shouldn't matter.
1490 let parent_module = find_nearest_parent_module(cx.tcx(), item_did.expect_def_id());
1491
1492 if vis_did.is_crate_root() {
1493 "pub(crate) ".into()
1494 } else if parent_module == Some(vis_did) {
1495 // `pub(in foo)` where `foo` is the parent module
1496 // is the same as no visibility modifier
1497 "".into()
1498 } else if parent_module.and_then(|parent| find_nearest_parent_module(cx.tcx(), parent))
1499 == Some(vis_did)
1500 {
1501 "pub(super) ".into()
1502 } else {
1503 let path = cx.tcx().def_path(vis_did);
1504 debug!("path={:?}", path);
1505 // modified from `resolved_path()` to work with `DefPathData`
1506 let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
1507 let anchor = anchor(vis_did, last_name, cx);
1508
1509 let mut s = "pub(in ".to_owned();
1510 for seg in &path.data[..path.data.len() - 1] {
1511 let _ = write!(s, "{}::", seg.data.get_opt_name().unwrap());
1512 }
1513 let _ = write!(s, "{}) ", anchor);
1514 s.into()
1515 }
1516 }
1517 };
1518 display_fn(move |f| write!(f, "{}", to_print))
1519 }
1520
1521 /// This function is the same as print_with_space, except that it renders no links.
1522 /// It's used for macros' rendered source view, which is syntax highlighted and cannot have
1523 /// any HTML in it.
visibility_to_src_with_space<'a, 'tcx: 'a>( visibility: Option<ty::Visibility<DefId>>, tcx: TyCtxt<'tcx>, item_did: DefId, ) -> impl fmt::Display + 'a + Captures<'tcx>1524 pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>(
1525 visibility: Option<ty::Visibility<DefId>>,
1526 tcx: TyCtxt<'tcx>,
1527 item_did: DefId,
1528 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1529 let to_print: Cow<'static, str> = match visibility {
1530 None => "".into(),
1531 Some(ty::Visibility::Public) => "pub ".into(),
1532 Some(ty::Visibility::Restricted(vis_did)) => {
1533 // FIXME(camelid): This may not work correctly if `item_did` is a module.
1534 // However, rustdoc currently never displays a module's
1535 // visibility, so it shouldn't matter.
1536 let parent_module = find_nearest_parent_module(tcx, item_did);
1537
1538 if vis_did.is_crate_root() {
1539 "pub(crate) ".into()
1540 } else if parent_module == Some(vis_did) {
1541 // `pub(in foo)` where `foo` is the parent module
1542 // is the same as no visibility modifier
1543 "".into()
1544 } else if parent_module.and_then(|parent| find_nearest_parent_module(tcx, parent))
1545 == Some(vis_did)
1546 {
1547 "pub(super) ".into()
1548 } else {
1549 format!("pub(in {}) ", tcx.def_path_str(vis_did)).into()
1550 }
1551 }
1552 };
1553 display_fn(move |f| f.write_str(&to_print))
1554 }
1555
1556 pub(crate) trait PrintWithSpace {
print_with_space(&self) -> &str1557 fn print_with_space(&self) -> &str;
1558 }
1559
1560 impl PrintWithSpace for hir::Unsafety {
print_with_space(&self) -> &str1561 fn print_with_space(&self) -> &str {
1562 match self {
1563 hir::Unsafety::Unsafe => "unsafe ",
1564 hir::Unsafety::Normal => "",
1565 }
1566 }
1567 }
1568
1569 impl PrintWithSpace for hir::IsAsync {
print_with_space(&self) -> &str1570 fn print_with_space(&self) -> &str {
1571 match self {
1572 hir::IsAsync::Async => "async ",
1573 hir::IsAsync::NotAsync => "",
1574 }
1575 }
1576 }
1577
1578 impl PrintWithSpace for hir::Mutability {
print_with_space(&self) -> &str1579 fn print_with_space(&self) -> &str {
1580 match self {
1581 hir::Mutability::Not => "",
1582 hir::Mutability::Mut => "mut ",
1583 }
1584 }
1585 }
1586
print_constness_with_space( c: &hir::Constness, s: Option<ConstStability>, ) -> &'static str1587 pub(crate) fn print_constness_with_space(
1588 c: &hir::Constness,
1589 s: Option<ConstStability>,
1590 ) -> &'static str {
1591 match (c, s) {
1592 // const stable or when feature(staged_api) is not set
1593 (
1594 hir::Constness::Const,
1595 Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }),
1596 )
1597 | (hir::Constness::Const, None) => "const ",
1598 // const unstable or not const
1599 _ => "",
1600 }
1601 }
1602
1603 impl clean::Import {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1604 pub(crate) fn print<'a, 'tcx: 'a>(
1605 &'a self,
1606 cx: &'a Context<'tcx>,
1607 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1608 display_fn(move |f| match self.kind {
1609 clean::ImportKind::Simple(name) => {
1610 if name == self.source.path.last() {
1611 write!(f, "use {};", self.source.print(cx))
1612 } else {
1613 write!(f, "use {} as {};", self.source.print(cx), name)
1614 }
1615 }
1616 clean::ImportKind::Glob => {
1617 if self.source.path.segments.is_empty() {
1618 write!(f, "use *;")
1619 } else {
1620 write!(f, "use {}::*;", self.source.print(cx))
1621 }
1622 }
1623 })
1624 }
1625 }
1626
1627 impl clean::ImportSource {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1628 pub(crate) fn print<'a, 'tcx: 'a>(
1629 &'a self,
1630 cx: &'a Context<'tcx>,
1631 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1632 display_fn(move |f| match self.did {
1633 Some(did) => resolved_path(f, did, &self.path, true, false, cx),
1634 _ => {
1635 for seg in &self.path.segments[..self.path.segments.len() - 1] {
1636 write!(f, "{}::", seg.name)?;
1637 }
1638 let name = self.path.last();
1639 if let hir::def::Res::PrimTy(p) = self.path.res {
1640 primitive_link(f, PrimitiveType::from(p), name.as_str(), cx)?;
1641 } else {
1642 write!(f, "{}", name)?;
1643 }
1644 Ok(())
1645 }
1646 })
1647 }
1648 }
1649
1650 impl clean::TypeBinding {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1651 pub(crate) fn print<'a, 'tcx: 'a>(
1652 &'a self,
1653 cx: &'a Context<'tcx>,
1654 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1655 display_fn(move |f| {
1656 f.write_str(self.assoc.name.as_str())?;
1657 if f.alternate() {
1658 write!(f, "{:#}", self.assoc.args.print(cx))?;
1659 } else {
1660 write!(f, "{}", self.assoc.args.print(cx))?;
1661 }
1662 match self.kind {
1663 clean::TypeBindingKind::Equality { ref term } => {
1664 if f.alternate() {
1665 write!(f, " = {:#}", term.print(cx))?;
1666 } else {
1667 write!(f, " = {}", term.print(cx))?;
1668 }
1669 }
1670 clean::TypeBindingKind::Constraint { ref bounds } => {
1671 if !bounds.is_empty() {
1672 if f.alternate() {
1673 write!(f, ": {:#}", print_generic_bounds(bounds, cx))?;
1674 } else {
1675 write!(f, ": {}", print_generic_bounds(bounds, cx))?;
1676 }
1677 }
1678 }
1679 }
1680 Ok(())
1681 })
1682 }
1683 }
1684
print_abi_with_space(abi: Abi) -> impl fmt::Display1685 pub(crate) fn print_abi_with_space(abi: Abi) -> impl fmt::Display {
1686 display_fn(move |f| {
1687 let quot = if f.alternate() { "\"" } else { """ };
1688 match abi {
1689 Abi::Rust => Ok(()),
1690 abi => write!(f, "extern {0}{1}{0} ", quot, abi.name()),
1691 }
1692 })
1693 }
1694
print_default_space<'a>(v: bool) -> &'a str1695 pub(crate) fn print_default_space<'a>(v: bool) -> &'a str {
1696 if v { "default " } else { "" }
1697 }
1698
1699 impl clean::GenericArg {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1700 pub(crate) fn print<'a, 'tcx: 'a>(
1701 &'a self,
1702 cx: &'a Context<'tcx>,
1703 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1704 display_fn(move |f| match self {
1705 clean::GenericArg::Lifetime(lt) => fmt::Display::fmt(<.print(), f),
1706 clean::GenericArg::Type(ty) => fmt::Display::fmt(&ty.print(cx), f),
1707 clean::GenericArg::Const(ct) => fmt::Display::fmt(&ct.print(cx.tcx()), f),
1708 clean::GenericArg::Infer => fmt::Display::fmt("_", f),
1709 })
1710 }
1711 }
1712
1713 impl clean::types::Term {
print<'a, 'tcx: 'a>( &'a self, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx>1714 pub(crate) fn print<'a, 'tcx: 'a>(
1715 &'a self,
1716 cx: &'a Context<'tcx>,
1717 ) -> impl fmt::Display + 'a + Captures<'tcx> {
1718 display_fn(move |f| match self {
1719 clean::types::Term::Type(ty) => fmt::Display::fmt(&ty.print(cx), f),
1720 clean::types::Term::Constant(ct) => fmt::Display::fmt(&ct.print(cx.tcx()), f),
1721 })
1722 }
1723 }
1724
display_fn( f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result, ) -> impl fmt::Display1725 pub(crate) fn display_fn(
1726 f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
1727 ) -> impl fmt::Display {
1728 struct WithFormatter<F>(Cell<Option<F>>);
1729
1730 impl<F> fmt::Display for WithFormatter<F>
1731 where
1732 F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
1733 {
1734 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1735 (self.0.take()).unwrap()(f)
1736 }
1737 }
1738
1739 WithFormatter(Cell::new(Some(f)))
1740 }
1741