1use std::cmp::Ordering;
10use std::fmt::{self, Display, Write};
11use std::{iter, slice};
12
13use itertools::{Either, Itertools};
14use rustc_abi::ExternAbi;
15use rustc_ast::join_path_syms;
16use rustc_attr_ir::{ConstStability, StabilityLevel, StableSince};
17use rustc_data_structures::fx::FxHashSet;
18use rustc_hir as hir;
19use rustc_hir::def::{DefKind, MacroKinds};
20use rustc_hir::def_id::{DefId, LOCAL_CRATE};
21use rustc_metadata::creader::CStore;
22use rustc_middle::ty::{self, Ty, TyCtxt, TypingMode};
23use rustc_span::symbol::kw;
24use rustc_span::{Ident, Symbol};
25use tracing::{debug, trace};
26
27use super::url_parts_builder::UrlPartsBuilder;
28use crate::clean::types::ExternalLocation;
29use crate::clean::utils::find_nearest_parent_module;
30use crate::clean::{self, ExternalCrate, PrimitiveType, WherePredicate};
31use crate::display::{Joined as _, MaybeDisplay as _, WithOpts, Wrapped};
32use crate::formats::cache::Cache;
33use crate::formats::item_type::ItemType;
34use crate::html::escape::{Escape, EscapeBodyText};
35use crate::html::render::Context;
36use crate::passes::collect_intra_doc_links::UrlFragment;
37
38pub(crate) fn print_generic_bounds(
39 bounds: &[clean::GenericBound],
40 cx: &Context<'_>,
41) -> impl Display {
42 fmt::from_fn(move |f| {
43 let mut bounds_dup = FxHashSet::default();
44
45 bounds
46 .iter()
47 .filter(move |b| bounds_dup.insert(*b))
48 .map(|bound| print_generic_bound(bound, cx))
49 .joined(" + ", f)
50 })
51}
52
53pub(crate) fn print_generic_param_def(
54 generic_param: &clean::GenericParamDef,
55 cx: &Context<'_>,
56) -> impl Display {
57 fmt::from_fn(move |f| match &generic_param.kind {
58 clean::GenericParamDefKind::Lifetime { outlives } => {
59 write!(f, "{}", generic_param.name)?;
60
61 if !outlives.is_empty() {
62 f.write_str(": ")?;
63 outlives.iter().map(|lt| print_lifetime(lt)).joined(" + ", f)?;
64 }
65
66 Ok(())
67 }
68 clean::GenericParamDefKind::Type { bounds, default, .. } => {
69 f.write_str(generic_param.name.as_str())?;
70
71 if !bounds.is_empty() {
72 f.write_str(": ")?;
73 print_generic_bounds(bounds, cx).fmt(f)?;
74 }
75
76 if let Some(ty) = default {
77 f.write_str(" = ")?;
78 print_type(ty, cx).fmt(f)?;
79 }
80
81 Ok(())
82 }
83 clean::GenericParamDefKind::Const { ty, default, .. } => {
84 write!(f, "const {}: ", generic_param.name)?;
85 print_type(ty, cx).fmt(f)?;
86
87 if let Some(default) = default {
88 f.write_str(" = ")?;
89 if f.alternate() {
90 write!(f, "{default}")?;
91 } else {
92 write!(f, "{}", Escape(default))?;
93 }
94 }
95
96 Ok(())
97 }
98 })
99}
100
101pub(crate) fn print_generics(generics: &clean::Generics, cx: &Context<'_>) -> impl Display {
102 let mut real_params = generics.params.iter().filter(|p| !p.is_synthetic_param()).peekable();
103 if real_params.peek().is_none() {
104 None
105 } else {
106 Some(Wrapped::with_angle_brackets().wrap_fn(move |f| {
107 real_params.clone().map(|g| print_generic_param_def(g, cx)).joined(", ", f)
108 }))
109 }
110 .maybe_display()
111}
112
113#[derive(Clone, Copy, PartialEq, Eq)]
114pub(crate) enum Ending {
115 Newline,
116 NoNewline,
117}
118
119fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display {
120 fmt::from_fn(move |f| {
121 match predicate {
122 clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
123 print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
124 print_type(ty, cx).fmt(f)?;
125 f.write_str(":")?;
126 if !bounds.is_empty() {
127 f.write_str(" ")?;
128 print_generic_bounds(bounds, cx).fmt(f)?;
129 }
130 Ok(())
131 }
132 clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
133 write!(f, "{}:", print_lifetime(lifetime))?;
136 if !bounds.is_empty() {
137 write!(f, " {}", print_generic_bounds(bounds, cx))?;
138 }
139 Ok(())
140 }
141 clean::WherePredicate::ProjectionPredicate { lhs, rhs } => {
142 let opts = WithOpts::from(f);
143 write!(
144 f,
145 "{} == {}",
146 opts.display(print_qpath_data(lhs, cx)),
147 opts.display(print_term(rhs, cx)),
148 )
149 }
150 }
151 })
152}
153
154pub(crate) fn print_where_clause(
158 gens: &clean::Generics,
159 cx: &Context<'_>,
160 indent: usize,
161 ending: Ending,
162) -> Option<impl Display> {
163 if gens.where_predicates.is_empty() {
164 return None;
165 }
166
167 fn where_preds(
168 predicates: &[WherePredicate],
169 cx: &Context<'_>,
170 sep: impl Display,
171 ) -> impl Display {
172 fmt::from_fn(move |f| {
173 predicates.iter().map(|predicate| print_where_predicate(predicate, cx)).joined(&sep, f)
174 })
175 }
176
177 let spaces = |n: usize| crate::display::repeat(' ', n);
178
179 Some(fmt::from_fn(move |f| {
180 if f.alternate() {
181 write!(f, " where {:#}", where_preds(&gens.where_predicates, cx, ", "))?;
182 if ending == Ending::Newline {
183 f.write_char(',')?;
184 }
185 return Ok(());
186 }
187
188 const WHERE_INDENT: usize = 3;
189
190 let padding = {
191 let padding_amount = if ending == Ending::Newline {
192 indent + 4
193 } else if indent == 0 {
194 4
195 } else {
196 indent + WHERE_INDENT + "where ".len()
197 };
198 spaces(padding_amount)
199 };
200
201 let br_with_padding = format_args!("\n{padding}");
202 let sep = format_args!(",{br_with_padding}");
203 let where_preds = where_preds(&gens.where_predicates, cx, sep);
204
205 if ending == Ending::Newline {
206 write!(
207 f,
208 "{indent}<div class=\"where\">where{br_with_padding}{where_preds},</div>",
209 indent = spaces(indent.saturating_sub(1)),
210 )
211 } else if indent == 0 {
212 write!(f, "\n<span class=\"where\">where{br_with_padding}{where_preds}</span>")
213 } else {
214 write!(
215 f,
216 "\n{indent}<span class=\"where\">where {where_preds}</span>",
217 indent = spaces(indent + WHERE_INDENT),
218 )
219 }
220 }))
221}
222
223#[inline]
224pub(crate) fn print_lifetime(lt: &clean::Lifetime) -> &str {
225 lt.0.as_str()
226}
227
228pub(crate) fn print_constant_kind(
229 constant_kind: &clean::ConstantKind,
230 tcx: TyCtxt<'_>,
231) -> impl Display {
232 let expr = constant_kind.expr(tcx);
233 fmt::from_fn(
234 move |f| {
235 if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) }
236 },
237 )
238}
239
240fn print_poly_trait(poly_trait: &clean::PolyTrait, cx: &Context<'_>) -> impl Display {
241 fmt::from_fn(move |f| {
242 print_higher_ranked_params_with_space(&poly_trait.generic_params, cx, "for").fmt(f)?;
243 print_path(&poly_trait.trait_, cx).fmt(f)
244 })
245}
246
247pub(crate) fn print_generic_bound(
248 generic_bound: &clean::GenericBound,
249 cx: &Context<'_>,
250) -> impl Display {
251 fmt::from_fn(move |f| match generic_bound {
252 clean::GenericBound::Outlives(lt) => f.write_str(print_lifetime(lt)),
253 clean::GenericBound::TraitBound(ty, modifiers) => {
254 let hir::TraitBoundModifiers { polarity, constness: _ } = modifiers;
256 f.write_str(match polarity {
257 hir::BoundPolarity::Positive => "",
258 hir::BoundPolarity::Maybe(_) => "?",
259 hir::BoundPolarity::Negative(_) => "!",
260 })?;
261 print_poly_trait(ty, cx).fmt(f)
262 }
263 clean::GenericBound::Use(args) => {
264 f.write_str("use")?;
265 Wrapped::with_angle_brackets()
266 .wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f))
267 .fmt(f)
268 }
269 })
270}
271
272fn print_generic_args(generic_args: &clean::GenericArgs, cx: &Context<'_>) -> impl Display {
273 fmt::from_fn(move |f| {
274 match generic_args {
275 clean::GenericArgs::AngleBracketed { args, constraints } => {
276 if !args.is_empty() || !constraints.is_empty() {
277 Wrapped::with_angle_brackets()
278 .wrap_fn(|f| {
279 [Either::Left(args), Either::Right(constraints)]
280 .into_iter()
281 .flat_map(Either::factor_into_iter)
282 .map(|either| {
283 either.map_either(
284 |arg| print_generic_arg(arg, cx),
285 |constraint| print_assoc_item_constraint(constraint, cx),
286 )
287 })
288 .joined(", ", f)
289 })
290 .fmt(f)?;
291 }
292 }
293 clean::GenericArgs::Parenthesized { inputs, output } => {
294 Wrapped::with_parens()
295 .wrap_fn(|f| inputs.iter().map(|ty| print_type(ty, cx)).joined(", ", f))
296 .fmt(f)?;
297 if let Some(ref ty) = *output {
298 f.write_str(if f.alternate() { " -> " } else { " -> " })?;
299 print_type(ty, cx).fmt(f)?;
300 }
301 }
302 clean::GenericArgs::ReturnTypeNotation => {
303 f.write_str("(..)")?;
304 }
305 }
306 Ok(())
307 })
308}
309
310#[derive(PartialEq, Eq)]
312pub(crate) enum HrefError {
313 DocumentationNotBuilt,
332 Private,
334 NotInExternalCache,
336 UnnamableItem,
338}
339
340pub(crate) struct HrefInfo {
342 pub(crate) url: String,
344 pub(crate) kind: ItemType,
346 pub(crate) rust_path: Vec<Symbol>,
348}
349
350fn generate_macro_def_id_path(
353 def_id: DefId,
354 cx: &Context<'_>,
355 root_path: Option<&str>,
356) -> Result<HrefInfo, HrefError> {
357 let tcx = cx.tcx();
358 let crate_name = tcx.crate_name(def_id.krate);
359 let cache = cx.cache();
360
361 let cstore = CStore::from_tcx(tcx);
362 if !cstore.has_crate_data(def_id.krate) {
364 debug!("No data for crate {crate_name}");
365 return Err(HrefError::NotInExternalCache);
366 }
367 let DefKind::Macro(kinds) = tcx.def_kind(def_id) else {
368 unreachable!();
369 };
370 let item_type = if kinds == MacroKinds::DERIVE {
371 ItemType::ProcDerive
372 } else if kinds == MacroKinds::ATTR {
373 ItemType::ProcAttribute
374 } else {
375 ItemType::Macro
376 };
377 let path = clean::inline::get_item_path(tcx, def_id, item_type);
378 let [module_path @ .., last] = path.as_slice() else {
381 debug!("macro path is empty!");
382 return Err(HrefError::NotInExternalCache);
383 };
384 if module_path.is_empty() {
385 debug!("macro path too short: missing crate prefix (got 1 element, need at least 2)");
386 return Err(HrefError::NotInExternalCache);
387 }
388
389 let url = match cache.extern_locations[&def_id.krate] {
390 ExternalLocation::Remote { ref url, is_absolute } => {
391 let mut prefix = remote_url_prefix(url, is_absolute, cx.current.len());
392 prefix.extend(module_path.iter().copied());
393 prefix.push_fmt(format_args!("{}.{last}.html", item_type.as_str()));
394 prefix.finish()
395 }
396 ExternalLocation::Local => {
397 format!(
399 "{root_path}{path}/{item_type}.{last}.html",
400 root_path = root_path.unwrap_or(""),
401 path = fmt::from_fn(|f| module_path.iter().joined("/", f)),
402 item_type = item_type.as_str(),
403 )
404 }
405 ExternalLocation::Unknown => {
406 debug!("crate {crate_name} not in cache when linkifying macros");
407 return Err(HrefError::NotInExternalCache);
408 }
409 };
410 Ok(HrefInfo { url, kind: item_type, rust_path: path })
411}
412
413fn impl_self_ty(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Ty<'_> {
415 use rustc_middle::traits::ObligationCause;
416 use rustc_middle::ty;
417 use rustc_trait_selection::infer::TyCtxtInferExt;
418 use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
419
420 let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis());
421 let ty = tcx.type_of(impl_def_id);
422 infcx
423 .at(&ObligationCause::dummy(), tcx.param_env(impl_def_id))
424 .query_normalize(ty::Binder::dummy(ty.instantiate_identity().skip_norm_wip()))
425 .map(|resolved| infcx.deeply_resolve_ignoring_regions(resolved.value).skip_binder())
426 .unwrap_or(ty.skip_binder())
427}
428
429fn transitive_reexport_path(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Vec<Symbol>> {
430 transitive_reexport_path_inner(tcx, def_id, &mut Vec::new())
431}
432
433fn transitive_reexport_path_inner(
435 tcx: TyCtxt<'_>,
436 def_id: DefId,
437 callers: &mut Vec<DefId>,
438) -> Option<Vec<Symbol>> {
439 use rustc_hir::def_id::ModId;
440 use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
441
442 if let Some(cnum) = def_id.as_crate_root() {
443 return Some(vec![tcx.crate_name(cnum)]);
444 }
445
446 let visible_parent_map = tcx.visible_parent_map(());
447 let mut cur_def_key = tcx.def_key(def_id);
448
449 if let DefPathData::Ctor = cur_def_key.disambiguated_data.data {
451 let parent = DefId {
452 krate: def_id.krate,
453 index: cur_def_key
454 .parent
455 .expect("`DefPathData::Ctor` / `VariantData` missing a parent"),
456 };
457
458 cur_def_key = tcx.def_key(parent);
459 }
460
461 let visible_parent = visible_parent_map.get(&def_id).cloned()?;
462 if tcx.is_doc_hidden(visible_parent) {
464 return None;
465 }
466
467 let actual_parent = tcx.opt_parent(def_id);
468 let mut data = cur_def_key.disambiguated_data.data;
469 match data {
470 DefPathData::TypeNs(ref mut name) if Some(visible_parent) != actual_parent => {
471 let reexport = tcx
474 .module_children(ModId::new_unchecked(visible_parent))
475 .iter()
476 .filter(|child| child.res.opt_def_id() == Some(def_id))
477 .find(|child| child.vis.is_public() && child.ident.name != kw::Underscore)
478 .map(|child| child.ident.name);
479
480 if let Some(new_name) = reexport {
481 *name = new_name;
482 } else {
483 return None;
485 }
486 }
487 DefPathData::CrateRoot => {
489 data = DefPathData::TypeNs(tcx.crate_name(def_id.krate));
490 }
491 _ => {}
492 }
493
494 if callers.contains(&visible_parent) {
495 return None;
496 }
497 callers.push(visible_parent);
498 let mut path = transitive_reexport_path_inner(tcx, visible_parent, callers)?;
499 callers.pop();
500 path.push(DisambiguatedDefPathData { data, disambiguator: 0 }.as_sym(false));
501 Some(path)
502}
503
504fn generate_item_def_id_path(
505 mut def_id: DefId,
506 original_def_id: DefId,
507 cx: &Context<'_>,
508 root_path: Option<&str>,
509) -> Result<HrefInfo, HrefError> {
510 let tcx = cx.tcx();
511 let crate_name = tcx.crate_name(def_id.krate);
512 let mut prim = None;
513 let mut maybe_have_impl_not_in_def_crate = false;
514
515 if def_id != original_def_id
518 && let DefKind::Impl { of_trait } = tcx.def_kind(def_id)
519 {
520 let ty = impl_self_ty(tcx, def_id);
521 if let ty::Dynamic(traits, _) = ty.kind()
525 && let Some(trait_def_id) =
526 traits.iter().find_map(|trait_| match trait_.skip_binder() {
527 ty::ExistentialPredicate::Trait(t) => Some(t.def_id),
528 ty::ExistentialPredicate::Projection(p) => Some(p.trait_ref(tcx).def_id),
529 ty::ExistentialPredicate::AutoTrait(_) => None,
530 })
531 {
532 def_id = trait_def_id;
533 } else if let Some(new_def_id) = ty.ty_adt_def().map(|adt| adt.did()) {
534 def_id = new_def_id;
535 maybe_have_impl_not_in_def_crate = !of_trait
536 && !original_def_id.is_local()
537 && !def_id.is_local()
538 && def_id.krate != original_def_id.krate;
539 } else {
540 prim = PrimitiveType::from_ty(ty);
541 }
542 }
543
544 let (shortty, fqp) = if let Some(prim) = prim {
545 (ItemType::Primitive, vec![crate_name, prim.as_sym()])
546 } else {
547 (
548 ItemType::from_def_id(def_id, tcx),
549 if maybe_have_impl_not_in_def_crate
550 && let Some(fqp) = transitive_reexport_path(tcx, def_id)
556 {
557 fqp
558 } else {
559 let mut fqp = vec![crate_name];
560 fqp.append(&mut clean::inline::item_relative_path(tcx, def_id));
561 fqp
562 },
563 )
564 };
565 let module_fqp = to_module_fqp(shortty, &fqp);
566
567 let (parts, is_absolute) = url_parts(cx.cache(), def_id, module_fqp, &cx.current)?;
568 let mut url = make_href(root_path, shortty, parts, &fqp, is_absolute);
569
570 if def_id != original_def_id {
571 let kind = ItemType::from_def_id(original_def_id, tcx);
572 url = format!("{url}#{kind}.{}", tcx.item_name(original_def_id))
573 };
574 Ok(HrefInfo { url, kind: shortty, rust_path: fqp })
575}
576
577fn is_unnamable(tcx: TyCtxt<'_>, did: DefId) -> bool {
579 let mut cur_did = did;
580 while let Some(parent) = tcx.opt_parent(cur_did) {
581 match tcx.def_kind(parent) {
582 DefKind::Mod | DefKind::ForeignMod => cur_did = parent,
584 DefKind::Impl { .. } => return false,
590 _ => return true,
592 }
593 }
594 return false;
595}
596
597fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
598 if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
599}
600
601fn remote_url_prefix(url: &str, is_absolute: bool, depth: usize) -> UrlPartsBuilder {
602 let url = url.trim_end_matches('/');
603 if is_absolute {
604 UrlPartsBuilder::singleton(url)
605 } else {
606 let extra = depth.saturating_sub(1);
607 let mut b: UrlPartsBuilder = iter::repeat_n("..", extra).collect();
608 b.push(url);
609 b
610 }
611}
612
613fn url_parts(
614 cache: &Cache,
615 def_id: DefId,
616 module_fqp: &[Symbol],
617 relative_to: &[Symbol],
618) -> Result<(UrlPartsBuilder, bool), HrefError> {
619 match cache.extern_locations[&def_id.krate] {
620 ExternalLocation::Remote { ref url, is_absolute } => {
621 let mut builder = remote_url_prefix(url, is_absolute, relative_to.len());
622 builder.extend(module_fqp.iter().copied());
623 Ok((builder, is_absolute))
624 }
625 ExternalLocation::Local => Ok((href_relative_parts(module_fqp, relative_to), false)),
626 ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
627 }
628}
629
630fn make_href(
631 root_path: Option<&str>,
632 shortty: ItemType,
633 mut url_parts: UrlPartsBuilder,
634 fqp: &[Symbol],
635 is_absolute: bool,
636) -> String {
637 if !is_absolute && let Some(root_path) = root_path {
639 let root = root_path.trim_end_matches('/');
640 url_parts.push_front(root);
641 }
642 debug!(?url_parts);
643 match shortty {
644 ItemType::Module => {
645 url_parts.push("index.html");
646 }
647 _ => {
648 let last = fqp.last().unwrap();
649 url_parts.push_fmt(format_args!("{shortty}.{last}.html"));
650 }
651 }
652 url_parts.finish()
653}
654
655fn ty_inherits_doc_hidden<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
656 match ty.kind() {
657 ty::Dynamic(traits, _) => traits
661 .iter()
662 .find_map(|trait_| match trait_.skip_binder() {
663 ty::ExistentialPredicate::Trait(t) => Some(inherits_doc_hidden(tcx, t.def_id)),
664 ty::ExistentialPredicate::Projection(p) => {
665 Some(inherits_doc_hidden(tcx, p.trait_ref(tcx).def_id))
666 }
667 ty::ExistentialPredicate::AutoTrait(_) => None,
668 })
669 .unwrap_or(false),
670 ty::Adt(adt, _) => inherits_doc_hidden(tcx, adt.did()),
671 ty::Foreign(def_id) => inherits_doc_hidden(tcx, *def_id),
672 ty::Ref(_, ty, _) => ty_inherits_doc_hidden(tcx, *ty),
673 _ => false,
676 }
677}
678
679fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut def_id: DefId) -> bool {
680 loop {
681 if tcx.is_doc_hidden(def_id) {
682 return true;
683 } else if def_id.is_crate_root() {
684 return false;
685 } else if let DefKind::Impl { of_trait } = tcx.def_kind(def_id) {
686 return ty_inherits_doc_hidden(tcx, impl_self_ty(tcx, def_id))
690 || (of_trait && inherits_doc_hidden(tcx, tcx.impl_trait_id(def_id)));
691 }
692 def_id = tcx.parent(def_id);
693 }
694}
695
696pub(crate) fn href_with_root_path(
697 original_did: DefId,
698 cx: &Context<'_>,
699 root_path: Option<&str>,
700 preferred_name: Option<&str>,
701) -> Result<HrefInfo, HrefError> {
702 let tcx = cx.tcx();
703 let def_kind = tcx.def_kind(original_did);
704 let did = match def_kind {
705 DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
706 tcx.parent(original_did)
708 }
709 DefKind::Ctor(..) => {
712 return href_with_root_path(tcx.parent(original_did), cx, root_path, preferred_name);
713 }
714 DefKind::ExternCrate => {
715 if let Some(local_did) = original_did.as_local() {
717 tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
718 } else {
719 original_did
720 }
721 }
722 _ => original_did,
723 };
724 if is_unnamable(tcx, did) {
725 return Err(HrefError::UnnamableItem);
726 }
727 let cache = cx.cache();
728 let relative_to = &cx.current;
729
730 if !original_did.is_local() {
731 if root_path.is_some() {
734 if inherits_doc_hidden(tcx, original_did) {
735 return Err(HrefError::Private);
736 }
737 } else if !cache.effective_visibilities.is_directly_public(tcx, did)
738 && !cache.document_private
739 && !cache.primitive_locations.values().any(|&id| id == did)
740 {
741 return Err(HrefError::Private);
742 }
743 }
744
745 let (fqp, shortty, url_parts, is_absolute) = match cache.paths.get(&did) {
746 Some(info) => (
747 info.get_preferred_path(preferred_name),
748 info.ty,
749 {
750 let module_fqp = to_module_fqp(info.ty, info.parts.as_slice());
751 debug!(?info.parts, ?info.ty, ?module_fqp);
752 href_relative_parts(module_fqp, relative_to)
753 },
754 false,
755 ),
756 None => {
757 let def_id_to_get = if root_path.is_some() { original_did } else { did };
761 if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
762 let module_fqp = to_module_fqp(shortty, fqp);
763 let (parts, is_absolute) = url_parts(cache, did, module_fqp, relative_to)?;
764 (fqp.as_slice(), shortty, parts, is_absolute)
765 } else if matches!(def_kind, DefKind::Macro(_)) {
766 return generate_macro_def_id_path(did, cx, root_path);
767 } else if did.is_local() {
768 return Err(HrefError::Private);
769 } else {
770 return generate_item_def_id_path(did, original_did, cx, root_path);
771 }
772 }
773 };
774 Ok(HrefInfo {
775 url: make_href(root_path, shortty, url_parts, fqp, is_absolute),
776 kind: shortty,
777 rust_path: fqp.to_vec(),
778 })
779}
780
781pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result<HrefInfo, HrefError> {
782 href_with_root_path(did, cx, None, None)
783}
784
785pub(crate) fn href_with_path_check(
786 did: DefId,
787 cx: &Context<'_>,
788 text: &str,
789) -> Result<HrefInfo, HrefError> {
790 href_with_root_path(did, cx, None, Some(text))
791}
792
793pub(crate) fn href_relative_parts(fqp: &[Symbol], relative_to_fqp: &[Symbol]) -> UrlPartsBuilder {
797 for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
798 if f != r {
800 let dissimilar_part_count = relative_to_fqp.len() - i;
801 let fqp_module = &fqp[i..];
802 return iter::repeat_n("..", dissimilar_part_count)
803 .chain(fqp_module.iter().map(|s| s.as_str()))
804 .collect();
805 }
806 }
807 match relative_to_fqp.len().cmp(&fqp.len()) {
808 Ordering::Less => {
809 fqp[relative_to_fqp.len()..fqp.len()].iter().copied().collect()
811 }
812 Ordering::Greater => {
813 let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
815 iter::repeat_n("..", dissimilar_part_count).collect()
816 }
817 Ordering::Equal => {
818 UrlPartsBuilder::new()
820 }
821 }
822}
823
824pub(crate) fn link_tooltip(
825 did: DefId,
826 fragment: &Option<UrlFragment>,
827 cx: &Context<'_>,
828 preferred_name: Option<&str>,
829) -> impl fmt::Display {
830 fmt::from_fn(move |f| {
831 let cache = cx.cache();
832 let Some((fqp, shortty)) = cache
833 .paths
834 .get(&did)
835 .map(|info| (info.get_preferred_path(preferred_name), info.ty))
836 .or_else(|| {
837 cache.external_paths.get(&did).map(|(fqp, shortty)| (fqp.as_slice(), *shortty))
838 })
839 else {
840 return Ok(());
841 };
842 let fqp = if shortty == ItemType::Primitive {
843 slice::from_ref(fqp.last().unwrap())
845 } else {
846 fqp
847 };
848 if let &Some(UrlFragment::Item(id)) = fragment {
849 let tcx = cx.tcx();
850 write!(f, "{} ", tcx.def_descr(id))?;
851 for component in fqp {
852 write!(f, "{component}::")?;
853 }
854 if shortty == ItemType::Enum && tcx.def_kind(id) == DefKind::Field {
855 write!(f, "{}::", tcx.item_name(tcx.parent(id)))?;
856 }
857 write!(f, "{}", tcx.item_name(id))?;
858 } else if !fqp.is_empty() {
859 write!(f, "{shortty} ")?;
860 write!(f, "{}", join_path_syms(fqp))?;
861 }
862 Ok(())
863 })
864}
865
866fn resolved_path(
868 w: &mut fmt::Formatter<'_>,
869 did: DefId,
870 path: &clean::Path,
871 print_all: bool,
872 use_absolute: bool,
873 cx: &Context<'_>,
874) -> fmt::Result {
875 let last = path.segments.last().unwrap();
876
877 if print_all {
878 for seg in &path.segments[..path.segments.len() - 1] {
879 write!(w, "{}::", if seg.name == kw::PathRoot { "" } else { seg.name.as_str() })?;
880 }
881 }
882 if w.alternate() {
883 write!(w, "{}{:#}", last.name, print_generic_args(&last.args, cx))?;
884 } else {
885 let path = fmt::from_fn(|f| {
886 if use_absolute {
887 if let Ok(HrefInfo { rust_path, .. }) = href(did, cx) {
888 write!(
889 f,
890 "{path}::{anchor}",
891 path = join_path_syms(&rust_path[..rust_path.len() - 1]),
892 anchor = print_anchor(did, *rust_path.last().unwrap(), cx)
893 )
894 } else {
895 write!(f, "{}", last.name)
896 }
897 } else {
898 write!(f, "{}", print_anchor(did, last.name, cx))
899 }
900 });
901 write!(w, "{path}{args}", args = print_generic_args(&last.args, cx))?;
902 }
903 Ok(())
904}
905
906fn primitive_link(
907 f: &mut fmt::Formatter<'_>,
908 prim: clean::PrimitiveType,
909 name: fmt::Arguments<'_>,
910 cx: &Context<'_>,
911) -> fmt::Result {
912 primitive_link_fragment(f, prim, name, "", cx)
913}
914
915fn primitive_link_fragment(
916 f: &mut fmt::Formatter<'_>,
917 prim: clean::PrimitiveType,
918 name: fmt::Arguments<'_>,
919 fragment: &str,
920 cx: &Context<'_>,
921) -> fmt::Result {
922 let m = &cx.cache();
923 let mut needs_termination = false;
924 if !f.alternate() {
925 match m.primitive_locations.get(&prim) {
926 Some(&def_id) if def_id.is_local() => {
927 let len = cx.current.len();
928 let path = fmt::from_fn(|f| {
929 if len == 0 {
930 let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
931 write!(f, "{cname_sym}/")?;
932 } else {
933 for _ in 0..(len - 1) {
934 f.write_str("../")?;
935 }
936 }
937 Ok(())
938 });
939 write!(
940 f,
941 "<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">",
942 prim.as_sym()
943 )?;
944 needs_termination = true;
945 }
946 Some(&def_id) => {
947 let loc = match m.extern_locations[&def_id.krate] {
948 ExternalLocation::Remote { ref url, is_absolute } => {
949 let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
950 let mut builder = remote_url_prefix(url, is_absolute, cx.current.len());
951 builder.push(cname_sym.as_str());
952 Some(builder)
953 }
954 ExternalLocation::Local => {
955 let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
956 Some(if cx.current.first() == Some(&cname_sym) {
957 iter::repeat_n("..", cx.current.len() - 1).collect()
958 } else {
959 iter::repeat_n("..", cx.current.len())
960 .chain(iter::once(cname_sym.as_str()))
961 .collect()
962 })
963 }
964 ExternalLocation::Unknown => None,
965 };
966 if let Some(mut loc) = loc {
967 loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
968 write!(f, "<a class=\"primitive\" href=\"{}{fragment}\">", loc.finish())?;
969 needs_termination = true;
970 }
971 }
972 None => {}
973 }
974 }
975 Display::fmt(&name, f)?;
976 if needs_termination {
977 write!(f, "</a>")?;
978 }
979 Ok(())
980}
981
982fn print_tybounds(
983 bounds: &[clean::PolyTrait],
984 lt: &Option<clean::Lifetime>,
985 cx: &Context<'_>,
986) -> impl Display {
987 fmt::from_fn(move |f| {
988 bounds.iter().map(|bound| print_poly_trait(bound, cx)).joined(" + ", f)?;
989 if let Some(lt) = lt {
990 write!(f, " + {}", print_lifetime(lt))?;
993 }
994 Ok(())
995 })
996}
997
998fn print_higher_ranked_params_with_space(
999 params: &[clean::GenericParamDef],
1000 cx: &Context<'_>,
1001 keyword: &'static str,
1002) -> impl Display {
1003 fmt::from_fn(move |f| {
1004 if !params.is_empty() {
1005 f.write_str(keyword)?;
1006 Wrapped::with_angle_brackets()
1007 .wrap_fn(|f| {
1008 params.iter().map(|lt| print_generic_param_def(lt, cx)).joined(", ", f)
1009 })
1010 .fmt(f)?;
1011 f.write_char(' ')?;
1012 }
1013 Ok(())
1014 })
1015}
1016
1017pub(crate) fn fragment(did: DefId, tcx: TyCtxt<'_>) -> impl Display {
1018 fmt::from_fn(move |f| {
1019 let def_kind = tcx.def_kind(did);
1020 match def_kind {
1021 DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
1022 let item_type = ItemType::from_def_id(did, tcx);
1023 write!(f, "#{}.{}", item_type.as_str(), tcx.item_name(did))
1024 }
1025 DefKind::Field => {
1026 let parent_def_id = tcx.parent(did);
1027 f.write_char('#')?;
1028 if tcx.def_kind(parent_def_id) == DefKind::Variant {
1029 write!(f, "variant.{}.field", tcx.item_name(parent_def_id).as_str())?;
1030 } else {
1031 f.write_str("structfield")?;
1032 };
1033 write!(f, ".{}", tcx.item_name(did))
1034 }
1035 _ => Ok(()),
1036 }
1037 })
1038}
1039
1040pub(crate) fn print_anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display {
1041 fmt::from_fn(move |f| {
1042 if let Ok(HrefInfo { url, kind, rust_path }) = href(did, cx) {
1043 write!(
1044 f,
1045 r#"<a class="{kind}" href="{url}{anchor}" title="{kind} {path}">{text}</a>"#,
1046 anchor = fragment(did, cx.tcx()),
1047 path = join_path_syms(rust_path),
1048 text = EscapeBodyText(text.as_str()),
1049 )
1050 } else {
1051 f.write_str(text.as_str())
1052 }
1053 })
1054}
1055
1056fn fmt_type(
1057 t: &clean::Type,
1058 f: &mut fmt::Formatter<'_>,
1059 use_absolute: bool,
1060 cx: &Context<'_>,
1061) -> fmt::Result {
1062 trace!("fmt_type(t = {t:?})");
1063
1064 match t {
1065 clean::Generic(name) => f.write_str(name.as_str()),
1066 clean::SelfTy => f.write_str("Self"),
1067 clean::Type::Path { path } => {
1068 let did = path.def_id();
1070 resolved_path(f, did, path, path.is_assoc_ty(), use_absolute, cx)
1071 }
1072 clean::DynTrait(bounds, lt) => {
1073 f.write_str("dyn ")?;
1074 print_tybounds(bounds, lt, cx).fmt(f)
1075 }
1076 clean::Infer => write!(f, "_"),
1077 clean::Primitive(clean::PrimitiveType::Never) => {
1078 primitive_link(f, PrimitiveType::Never, format_args!("!"), cx)
1079 }
1080 &clean::Primitive(prim) => primitive_link(f, prim, format_args!("{}", prim.as_sym()), cx),
1081 clean::BareFunction(decl) => {
1082 print_higher_ranked_params_with_space(&decl.generic_params, cx, "for").fmt(f)?;
1083 decl.safety.print_with_space().fmt(f)?;
1084 print_abi_with_space(decl.abi).fmt(f)?;
1085 if f.alternate() {
1086 f.write_str("fn")?;
1087 } else {
1088 primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?;
1089 }
1090 print_fn_decl(&decl.decl, cx).fmt(f)
1091 }
1092 clean::UnsafeBinder(binder) => {
1093 print_higher_ranked_params_with_space(&binder.generic_params, cx, "unsafe").fmt(f)?;
1094 print_type(&binder.ty, cx).fmt(f)
1095 }
1096 clean::Tuple(typs) => match &typs[..] {
1097 &[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx),
1098 [one] => {
1099 if let clean::Generic(name) = one {
1100 primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx)
1101 } else {
1102 write!(f, "(")?;
1103 print_type(one, cx).fmt(f)?;
1104 write!(f, ",)")
1105 }
1106 }
1107 many => {
1108 let generic_names: Vec<Symbol> = many
1109 .iter()
1110 .filter_map(|t| match t {
1111 clean::Generic(name) => Some(*name),
1112 _ => None,
1113 })
1114 .collect();
1115 let is_generic = generic_names.len() == many.len();
1116 if is_generic {
1117 primitive_link(
1118 f,
1119 PrimitiveType::Tuple,
1120 format_args!(
1121 "{}",
1122 Wrapped::with_parens()
1123 .wrap_fn(|f| generic_names.iter().joined(", ", f))
1124 ),
1125 cx,
1126 )
1127 } else {
1128 Wrapped::with_parens()
1129 .wrap_fn(|f| many.iter().map(|item| print_type(item, cx)).joined(", ", f))
1130 .fmt(f)
1131 }
1132 }
1133 },
1134 clean::Slice(clean::Generic(name)) => {
1135 primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx)
1136 }
1137 clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f),
1138 clean::Type::Pat(t, pat) => {
1139 fmt::Display::fmt(&print_type(t, cx), f)?;
1140 write!(f, " is {pat}")
1141 }
1142 clean::Type::FieldOf(t, field) => {
1143 write!(f, "field_of!(")?;
1144 fmt::Display::fmt(&print_type(t, cx), f)?;
1145 write!(f, ", {field})")
1146 }
1147 clean::Array(clean::Generic(name), n) if !f.alternate() => primitive_link(
1148 f,
1149 PrimitiveType::Array,
1150 format_args!("[{name}; {n}]", n = Escape(n)),
1151 cx,
1152 ),
1153 clean::Array(t, n) => Wrapped::with_square_brackets()
1154 .wrap(fmt::from_fn(|f| {
1155 print_type(t, cx).fmt(f)?;
1156 f.write_str("; ")?;
1157 if f.alternate() {
1158 f.write_str(n)
1159 } else {
1160 primitive_link(f, PrimitiveType::Array, format_args!("{n}", n = Escape(n)), cx)
1161 }
1162 }))
1163 .fmt(f),
1164 clean::RawPointer(m, t) => {
1165 let m = m.ptr_str();
1166
1167 if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() {
1168 primitive_link(
1169 f,
1170 clean::PrimitiveType::RawPointer,
1171 format_args!("*{m} {ty}", ty = WithOpts::from(f).display(print_type(t, cx))),
1172 cx,
1173 )
1174 } else {
1175 primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?;
1176 print_type(t, cx).fmt(f)
1177 }
1178 }
1179 clean::BorrowedRef { lifetime: l, mutability, type_: ty } => {
1180 let lt = fmt::from_fn(|f| match l {
1181 Some(l) => write!(f, "{} ", print_lifetime(l)),
1182 _ => Ok(()),
1183 });
1184 let m = mutability.print_with_space();
1185 let amp = if f.alternate() { "&" } else { "&" };
1186
1187 if let clean::Generic(name) = **ty {
1188 return primitive_link(
1189 f,
1190 PrimitiveType::Reference,
1191 format_args!("{amp}{lt}{m}{name}"),
1192 cx,
1193 );
1194 }
1195
1196 write!(f, "{amp}{lt}{m}")?;
1197
1198 let needs_parens = match **ty {
1199 clean::DynTrait(ref bounds, ref trait_lt)
1200 if bounds.len() > 1 || trait_lt.is_some() =>
1201 {
1202 true
1203 }
1204 clean::ImplTrait(ref bounds) if bounds.len() > 1 => true,
1205 _ => false,
1206 };
1207 Wrapped::with_parens()
1208 .when(needs_parens)
1209 .wrap_fn(|f| fmt_type(ty, f, use_absolute, cx))
1210 .fmt(f)
1211 }
1212 clean::ImplTrait(bounds) => {
1213 f.write_str("impl ")?;
1214 print_generic_bounds(bounds, cx).fmt(f)
1215 }
1216 clean::QPath(qpath) => print_qpath_data(qpath, cx).fmt(f),
1217 }
1218}
1219
1220pub(crate) fn print_type(type_: &clean::Type, cx: &Context<'_>) -> impl Display {
1221 fmt::from_fn(move |f| fmt_type(type_, f, false, cx))
1222}
1223
1224pub(crate) fn print_path(path: &clean::Path, cx: &Context<'_>) -> impl Display {
1225 fmt::from_fn(move |f| resolved_path(f, path.def_id(), path, false, false, cx))
1226}
1227
1228fn print_qpath_data(qpath_data: &clean::QPathData, cx: &Context<'_>) -> impl Display {
1229 let clean::QPathData { ref assoc, ref self_type, should_fully_qualify, ref trait_ } =
1230 *qpath_data;
1231
1232 fmt::from_fn(move |f| {
1233 if let Some(trait_) = trait_
1237 && should_fully_qualify
1238 {
1239 let opts = WithOpts::from(f);
1240 Wrapped::with_angle_brackets()
1241 .wrap(format_args!(
1242 "{} as {}",
1243 opts.display(print_type(self_type, cx)),
1244 opts.display(print_path(trait_, cx))
1245 ))
1246 .fmt(f)?
1247 } else {
1248 print_type(self_type, cx).fmt(f)?;
1249 }
1250 f.write_str("::")?;
1251 if !f.alternate() {
1262 let parent_href = match trait_ {
1275 Some(trait_) => href(trait_.def_id(), cx).ok(),
1276 None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()),
1277 };
1278 let tcx = cx.tcx();
1279 let assoc_type_is_hidden = !cx.cache().document_hidden
1280 && trait_.as_ref().is_some_and(|trait_| {
1281 let trait_did = trait_.def_id();
1282 tcx.associated_items(trait_did)
1283 .find_by_ident_and_kind(
1284 tcx,
1285 Ident::with_dummy_span(assoc.name),
1286 ty::AssocTag::Type,
1287 trait_did,
1288 )
1289 .is_some_and(|assoc_item| tcx.is_doc_hidden(assoc_item.def_id))
1290 });
1291
1292 if let Some(HrefInfo { url, rust_path, .. }) = parent_href
1293 && !assoc_type_is_hidden
1294 {
1295 write!(
1296 f,
1297 "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
1298 title=\"type {path}::{name}\">{name}</a>",
1299 shortty = ItemType::AssocType,
1300 name = assoc.name,
1301 path = join_path_syms(rust_path),
1302 )
1303 } else {
1304 write!(f, "{}", assoc.name)
1305 }
1306 } else {
1307 write!(f, "{}", assoc.name)
1308 }?;
1309
1310 print_generic_args(&assoc.args, cx).fmt(f)
1311 })
1312}
1313
1314pub(crate) fn print_impl(
1315 impl_: &clean::Impl,
1316 use_absolute: bool,
1317 cx: &Context<'_>,
1318) -> impl Display {
1319 fmt::from_fn(move |f| {
1320 f.write_str("impl")?;
1321 print_generics(&impl_.generics, cx).fmt(f)?;
1322 f.write_str(" ")?;
1323
1324 if let Some(ref ty) = impl_.trait_ {
1325 if impl_.is_negative_trait_impl() {
1326 f.write_char('!')?;
1327 }
1328 if impl_.kind.is_fake_variadic()
1329 && let Some(generics) = ty.generics()
1330 && let Ok(inner_type) = generics.exactly_one()
1331 {
1332 let last = ty.last();
1333 if f.alternate() {
1334 write!(f, "{last}")?;
1335 } else {
1336 write!(f, "{}", print_anchor(ty.def_id(), last, cx))?;
1337 };
1338 Wrapped::with_angle_brackets()
1339 .wrap_fn(|f| impl_.print_type(inner_type, f, use_absolute, cx))
1340 .fmt(f)?;
1341 } else {
1342 print_path(ty, cx).fmt(f)?;
1343 }
1344 f.write_str(" for ")?;
1345 }
1346
1347 if let Some(ty) = impl_.kind.as_blanket_ty() {
1348 fmt_type(ty, f, use_absolute, cx)?;
1349 } else {
1350 impl_.print_type(&impl_.for_, f, use_absolute, cx)?;
1351 }
1352
1353 print_where_clause(&impl_.generics, cx, 0, Ending::Newline).maybe_display().fmt(f)
1354 })
1355}
1356
1357impl clean::Impl {
1358 fn print_type(
1359 &self,
1360 type_: &clean::Type,
1361 f: &mut fmt::Formatter<'_>,
1362 use_absolute: bool,
1363 cx: &Context<'_>,
1364 ) -> Result<(), fmt::Error> {
1365 if let clean::Type::Tuple(types) = type_
1366 && let [clean::Type::Generic(name)] = &types[..]
1367 && (self.kind.is_fake_variadic() || self.kind.is_auto())
1368 {
1369 primitive_link_fragment(
1372 f,
1373 PrimitiveType::Tuple,
1374 format_args!("({name}₁, {name}₂, …, {name}ₙ)"),
1375 "#trait-implementations-1",
1376 cx,
1377 )?;
1378 } else if let clean::Type::Array(ty, len) = type_
1379 && let clean::Type::Generic(name) = &**ty
1380 && &len[..] == "1"
1381 && (self.kind.is_fake_variadic() || self.kind.is_auto())
1382 {
1383 primitive_link(f, PrimitiveType::Array, format_args!("[{name}; N]"), cx)?;
1384 } else if let clean::BareFunction(bare_fn) = &type_
1385 && let [clean::Parameter { type_: clean::Type::Generic(name), .. }] =
1386 &bare_fn.decl.inputs[..]
1387 && (self.kind.is_fake_variadic() || self.kind.is_auto())
1388 {
1389 print_higher_ranked_params_with_space(&bare_fn.generic_params, cx, "for").fmt(f)?;
1393 bare_fn.safety.print_with_space().fmt(f)?;
1394 print_abi_with_space(bare_fn.abi).fmt(f)?;
1395 let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
1396 primitive_link_fragment(
1397 f,
1398 PrimitiveType::Tuple,
1399 format_args!("fn({name}₁, {name}₂, …, {name}ₙ{ellipsis})"),
1400 "#trait-implementations-1",
1401 cx,
1402 )?;
1403 if !bare_fn.decl.output.is_unit() {
1405 write!(f, " -> ")?;
1406 fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?;
1407 }
1408 } else if let clean::Type::Path { path } = type_
1409 && let Some(generics) = path.generics()
1410 && let Ok(ty) = generics.exactly_one()
1411 && self.kind.is_fake_variadic()
1412 {
1413 print_anchor(path.def_id(), path.last(), cx).fmt(f)?;
1414 Wrapped::with_angle_brackets()
1415 .wrap_fn(|f| self.print_type(ty, f, use_absolute, cx))
1416 .fmt(f)?;
1417 } else {
1418 fmt_type(type_, f, use_absolute, cx)?;
1419 }
1420 Ok(())
1421 }
1422}
1423
1424pub(crate) fn print_params(params: &[clean::Parameter], cx: &Context<'_>) -> impl Display {
1425 fmt::from_fn(move |f| {
1426 params
1427 .iter()
1428 .map(|param| {
1429 fmt::from_fn(|f| {
1430 if param.is_splat {
1431 write!(f, "…: ")?;
1432 } else if let Some(name) = param.name {
1433 write!(f, "{name}: ")?;
1434 }
1435 print_type(¶m.type_, cx).fmt(f)
1436 })
1437 })
1438 .joined(", ", f)
1439 })
1440}
1441
1442struct WriteCounter(usize);
1444
1445impl std::fmt::Write for WriteCounter {
1446 fn write_str(&mut self, s: &str) -> fmt::Result {
1447 self.0 += s.len();
1448 Ok(())
1449 }
1450}
1451
1452#[derive(Clone, Copy)]
1454struct Indent(usize);
1455
1456impl Display for Indent {
1457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1458 for _ in 0..self.0 {
1459 f.write_char(' ')?;
1460 }
1461 Ok(())
1462 }
1463}
1464
1465fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt::Display {
1466 fmt::from_fn(move |f| {
1467 if let Some(self_ty) = parameter.to_receiver() {
1468 match self_ty {
1469 clean::SelfTy => f.write_str("self"),
1470 clean::BorrowedRef { lifetime, mutability, type_: clean::SelfTy } => {
1471 f.write_str(if f.alternate() { "&" } else { "&" })?;
1472 if let Some(lt) = lifetime {
1473 write!(f, "{lt} ", lt = print_lifetime(lt))?;
1474 }
1475 write!(f, "{mutability}self", mutability = mutability.print_with_space())
1476 }
1477 _ => {
1478 f.write_str("self: ")?;
1479 print_type(self_ty, cx).fmt(f)
1480 }
1481 }
1482 } else {
1483 if parameter.is_const {
1484 write!(f, "const ")?;
1485 }
1486 if parameter.is_splat {
1487 write!(f, "…: ")?;
1488 } else if let Some(name) = parameter.name {
1489 write!(f, "{name}: ")?;
1490 }
1491 print_type(¶meter.type_, cx).fmt(f)
1492 }
1493 })
1494}
1495
1496fn print_fn_decl(fn_decl: &clean::FnDecl, cx: &Context<'_>) -> impl Display {
1497 fmt::from_fn(move |f| {
1498 let ellipsis = if fn_decl.c_variadic { ", ..." } else { "" };
1499 Wrapped::with_parens()
1500 .wrap_fn(|f| {
1501 print_params(&fn_decl.inputs, cx).fmt(f)?;
1502 f.write_str(ellipsis)
1503 })
1504 .fmt(f)?;
1505 fn_decl.print_output(cx).fmt(f)
1506 })
1507}
1508
1509pub(crate) fn full_print_fn_decl(
1516 fn_decl: &clean::FnDecl,
1517 header_len: usize,
1518 indent: usize,
1519 cx: &Context<'_>,
1520) -> impl Display {
1521 fmt::from_fn(move |f| {
1522 let mut counter = WriteCounter(0);
1524 write!(&mut counter, "{:#}", fmt::from_fn(|f| { fn_decl.inner_full_print(None, f, cx) }))?;
1525 let line_wrapping_indent = if header_len + counter.0 > 80 { Some(indent) } else { None };
1527 fn_decl.inner_full_print(line_wrapping_indent, f, cx)
1530 })
1531}
1532
1533impl clean::FnDecl {
1534 fn inner_full_print(
1535 &self,
1536 line_wrapping_indent: Option<usize>,
1539 f: &mut fmt::Formatter<'_>,
1540 cx: &Context<'_>,
1541 ) -> fmt::Result {
1542 Wrapped::with_parens()
1543 .wrap_fn(|f| {
1544 if !self.inputs.is_empty() {
1545 let line_wrapping_indent = line_wrapping_indent.map(|n| Indent(n + 4));
1546
1547 if let Some(indent) = line_wrapping_indent {
1548 write!(f, "\n{indent}")?;
1549 }
1550
1551 let sep = fmt::from_fn(|f| {
1552 if let Some(indent) = line_wrapping_indent {
1553 write!(f, ",\n{indent}")
1554 } else {
1555 f.write_str(", ")
1556 }
1557 });
1558
1559 self.inputs.iter().map(|param| print_parameter(param, cx)).joined(sep, f)?;
1560
1561 if line_wrapping_indent.is_some() {
1562 writeln!(f, ",")?
1563 }
1564
1565 if self.c_variadic {
1566 match line_wrapping_indent {
1567 None => write!(f, ", ...")?,
1568 Some(indent) => writeln!(f, "{indent}...")?,
1569 };
1570 }
1571 }
1572
1573 if let Some(n) = line_wrapping_indent {
1574 write!(f, "{}", Indent(n))?
1575 }
1576
1577 Ok(())
1578 })
1579 .fmt(f)?;
1580
1581 self.print_output(cx).fmt(f)
1582 }
1583
1584 fn print_output(&self, cx: &Context<'_>) -> impl Display {
1585 fmt::from_fn(move |f| {
1586 if self.output.is_unit() {
1587 return Ok(());
1588 }
1589
1590 f.write_str(if f.alternate() { " -> " } else { " -> " })?;
1591 print_type(&self.output, cx).fmt(f)
1592 })
1593 }
1594}
1595
1596pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) -> impl Display {
1597 fmt::from_fn(move |f| {
1598 let Some(vis) = item.visibility(cx.tcx()) else {
1599 return Ok(());
1600 };
1601
1602 match vis {
1603 ty::Visibility::Public => f.write_str("pub ")?,
1604 ty::Visibility::Restricted(vis_mod_id) => {
1605 let parent_module =
1609 find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id());
1610
1611 if vis_mod_id.is_crate_root() {
1612 f.write_str("pub(crate) ")?;
1613 } else if parent_module == Some(vis_mod_id) {
1614 } else if parent_module
1617 .and_then(|parent| find_nearest_parent_module(cx.tcx(), parent.to_def_id()))
1618 == Some(vis_mod_id)
1619 {
1620 f.write_str("pub(super) ")?;
1621 } else {
1622 let path = cx.tcx().def_path(vis_mod_id.to_def_id());
1623 debug!("path={path:?}");
1624 let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
1626 let anchor = print_anchor(vis_mod_id.to_def_id(), last_name, cx);
1627
1628 f.write_str("pub(in ")?;
1629 for seg in &path.data[..path.data.len() - 1] {
1630 write!(f, "{}::", seg.data.get_opt_name().unwrap())?;
1631 }
1632 write!(f, "{anchor}) ")?;
1633 }
1634 }
1635 }
1636 Ok(())
1637 })
1638}
1639
1640pub(crate) trait PrintWithSpace {
1641 fn print_with_space(&self) -> &str;
1642}
1643
1644impl PrintWithSpace for hir::Safety {
1645 fn print_with_space(&self) -> &str {
1646 self.prefix_str()
1647 }
1648}
1649
1650impl PrintWithSpace for hir::HeaderSafety {
1651 fn print_with_space(&self) -> &str {
1652 match self {
1653 hir::HeaderSafety::SafeTargetFeatures => "",
1654 hir::HeaderSafety::Normal(safety) => safety.print_with_space(),
1655 }
1656 }
1657}
1658
1659impl PrintWithSpace for hir::IsAsync {
1660 fn print_with_space(&self) -> &str {
1661 match self {
1662 hir::IsAsync::Async(_) => "async ",
1663 hir::IsAsync::NotAsync => "",
1664 }
1665 }
1666}
1667
1668impl PrintWithSpace for hir::Mutability {
1669 fn print_with_space(&self) -> &str {
1670 match self {
1671 hir::Mutability::Not => "",
1672 hir::Mutability::Mut => "mut ",
1673 }
1674 }
1675}
1676
1677pub(crate) fn print_constness_with_space(
1678 c: &hir::Constness,
1679 overall_stab: Option<StableSince>,
1680 const_stab: Option<ConstStability>,
1681) -> &'static str {
1682 match *c {
1683 hir::Constness::Const { always } => match (overall_stab, const_stab) {
1684 (_, Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }))
1686 | (_, None)
1688 | (None, Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => {
1690 if always {
1691 ""
1694 } else {
1695 "const "
1696 }
1697 }
1698 (Some(_), Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => "",
1700 },
1701 hir::Constness::NotConst => "",
1703 }
1704}
1705
1706pub(crate) fn print_import(import: &clean::Import, cx: &Context<'_>) -> impl Display {
1707 fmt::from_fn(move |f| match import.kind {
1708 clean::ImportKind::Simple(name) => {
1709 if name == import.source.path.last() {
1710 write!(f, "use {};", print_import_source(&import.source, cx))
1711 } else {
1712 write!(
1713 f,
1714 "use {source} as {name};",
1715 source = print_import_source(&import.source, cx)
1716 )
1717 }
1718 }
1719 clean::ImportKind::Glob => {
1720 if import.source.path.segments.is_empty() {
1721 write!(f, "use *;")
1722 } else {
1723 write!(f, "use {}::*;", print_import_source(&import.source, cx))
1724 }
1725 }
1726 })
1727}
1728
1729fn print_import_source(import_source: &clean::ImportSource, cx: &Context<'_>) -> impl Display {
1730 fmt::from_fn(move |f| match import_source.did {
1731 Some(did) => resolved_path(f, did, &import_source.path, true, false, cx),
1732 _ => {
1733 for seg in &import_source.path.segments[..import_source.path.segments.len() - 1] {
1734 write!(f, "{}::", seg.name)?;
1735 }
1736 let name = import_source.path.last();
1737 if let hir::def::Res::PrimTy(p) = import_source.path.res {
1738 primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
1739 } else {
1740 f.write_str(name.as_str())?;
1741 }
1742 Ok(())
1743 }
1744 })
1745}
1746
1747fn print_assoc_item_constraint(
1748 assoc_item_constraint: &clean::AssocItemConstraint,
1749 cx: &Context<'_>,
1750) -> impl Display {
1751 fmt::from_fn(move |f| {
1752 f.write_str(assoc_item_constraint.assoc.name.as_str())?;
1753 print_generic_args(&assoc_item_constraint.assoc.args, cx).fmt(f)?;
1754 match assoc_item_constraint.kind {
1755 clean::AssocItemConstraintKind::Equality { ref term } => {
1756 f.write_str(" = ")?;
1757 print_term(term, cx).fmt(f)?;
1758 }
1759 clean::AssocItemConstraintKind::Bound { ref bounds } => {
1760 if !bounds.is_empty() {
1761 f.write_str(": ")?;
1762 print_generic_bounds(bounds, cx).fmt(f)?;
1763 }
1764 }
1765 }
1766 Ok(())
1767 })
1768}
1769
1770pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display {
1771 fmt::from_fn(move |f| {
1772 let quot = if f.alternate() { "\"" } else { """ };
1773 match abi {
1774 ExternAbi::Rust => Ok(()),
1775 abi => write!(f, "extern {0}{1}{0} ", quot, abi.name()),
1776 }
1777 })
1778}
1779
1780fn print_generic_arg(generic_arg: &clean::GenericArg, cx: &Context<'_>) -> impl Display {
1781 fmt::from_fn(move |f| match generic_arg {
1782 clean::GenericArg::Lifetime(lt) => f.write_str(print_lifetime(lt)),
1783 clean::GenericArg::Type(ty) => print_type(ty, cx).fmt(f),
1784 clean::GenericArg::Const(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
1785 clean::GenericArg::Infer => f.write_char('_'),
1786 })
1787}
1788
1789fn print_term(term: &clean::Term, cx: &Context<'_>) -> impl Display {
1790 fmt::from_fn(move |f| match term {
1791 clean::Term::Type(ty) => print_type(ty, cx).fmt(f),
1792 clean::Term::Constant(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
1793 })
1794}