]> git.proxmox.com Git - rustc.git/blob - src/librustdoc/html/render/sidebar.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / src / librustdoc / html / render / sidebar.rs
1 use std::{borrow::Cow, rc::Rc};
2
3 use askama::Template;
4 use rustc_data_structures::fx::FxHashSet;
5 use rustc_hir::{def::CtorKind, def_id::DefIdSet};
6 use rustc_middle::ty::{self, TyCtxt};
7
8 use crate::{
9 clean,
10 formats::{item_type::ItemType, Impl},
11 html::{format::Buffer, markdown::IdMap},
12 };
13
14 use super::{item_ty_to_section, Context, ItemSection};
15
16 #[derive(Template)]
17 #[template(path = "sidebar.html")]
18 pub(super) struct Sidebar<'a> {
19 pub(super) title_prefix: &'static str,
20 pub(super) title: &'a str,
21 pub(super) is_crate: bool,
22 pub(super) version: &'a str,
23 pub(super) blocks: Vec<LinkBlock<'a>>,
24 pub(super) path: String,
25 }
26
27 impl<'a> Sidebar<'a> {
28 /// Only create a `<section>` if there are any blocks
29 /// which should actually be rendered.
30 pub fn should_render_blocks(&self) -> bool {
31 self.blocks.iter().any(LinkBlock::should_render)
32 }
33 }
34
35 /// A sidebar section such as 'Methods'.
36 pub(crate) struct LinkBlock<'a> {
37 /// The name of this section, e.g. 'Methods'
38 /// as well as the link to it, e.g. `#implementations`.
39 /// Will be rendered inside an `<h3>` tag
40 heading: Link<'a>,
41 links: Vec<Link<'a>>,
42 /// Render the heading even if there are no links
43 force_render: bool,
44 }
45
46 impl<'a> LinkBlock<'a> {
47 pub fn new(heading: Link<'a>, links: Vec<Link<'a>>) -> Self {
48 Self { heading, links, force_render: false }
49 }
50
51 pub fn forced(heading: Link<'a>) -> Self {
52 Self { heading, links: vec![], force_render: true }
53 }
54
55 pub fn should_render(&self) -> bool {
56 self.force_render || !self.links.is_empty()
57 }
58 }
59
60 /// A link to an item. Content should not be escaped.
61 #[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)]
62 pub(crate) struct Link<'a> {
63 /// The content for the anchor tag
64 name: Cow<'a, str>,
65 /// The id of an anchor within the page (without a `#` prefix)
66 href: Cow<'a, str>,
67 }
68
69 impl<'a> Link<'a> {
70 pub fn new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self {
71 Self { href: href.into(), name: name.into() }
72 }
73 pub fn empty() -> Link<'static> {
74 Link::new("", "")
75 }
76 }
77
78 pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
79 let blocks: Vec<LinkBlock<'_>> = match *it.kind {
80 clean::StructItem(ref s) => sidebar_struct(cx, it, s),
81 clean::TraitItem(ref t) => sidebar_trait(cx, it, t),
82 clean::PrimitiveItem(_) => sidebar_primitive(cx, it),
83 clean::UnionItem(ref u) => sidebar_union(cx, it, u),
84 clean::EnumItem(ref e) => sidebar_enum(cx, it, e),
85 clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t),
86 clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)],
87 clean::ForeignTypeItem => sidebar_foreign_type(cx, it),
88 _ => vec![],
89 };
90 // The sidebar is designed to display sibling functions, modules and
91 // other miscellaneous information. since there are lots of sibling
92 // items (and that causes quadratic growth in large modules),
93 // we refactor common parts into a shared JavaScript file per module.
94 // still, we don't move everything into JS because we want to preserve
95 // as much HTML as possible in order to allow non-JS-enabled browsers
96 // to navigate the documentation (though slightly inefficiently).
97 let (title_prefix, title) = if it.is_struct()
98 || it.is_trait()
99 || it.is_primitive()
100 || it.is_union()
101 || it.is_enum()
102 || it.is_mod()
103 || it.is_type_alias()
104 {
105 (
106 match *it.kind {
107 clean::ModuleItem(..) if it.is_crate() => "Crate ",
108 clean::ModuleItem(..) => "Module ",
109 _ => "",
110 },
111 it.name.as_ref().unwrap().as_str(),
112 )
113 } else {
114 ("", "")
115 };
116 let version =
117 if it.is_crate() { cx.cache().crate_version.as_deref().unwrap_or_default() } else { "" };
118 let path: String = if !it.is_mod() {
119 cx.current.iter().map(|s| s.as_str()).intersperse("::").collect()
120 } else {
121 "".into()
122 };
123 let sidebar = Sidebar { title_prefix, title, is_crate: it.is_crate(), version, blocks, path };
124 sidebar.render_into(buffer).unwrap();
125 }
126
127 fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>> {
128 let mut fields = fields
129 .iter()
130 .filter(|f| matches!(*f.kind, clean::StructFieldItem(..)))
131 .filter_map(|f| {
132 f.name.as_ref().map(|name| Link::new(format!("structfield.{name}"), name.as_str()))
133 })
134 .collect::<Vec<Link<'a>>>();
135 fields.sort();
136 fields
137 }
138
139 fn sidebar_struct<'a>(
140 cx: &'a Context<'_>,
141 it: &'a clean::Item,
142 s: &'a clean::Struct,
143 ) -> Vec<LinkBlock<'a>> {
144 let fields = get_struct_fields_name(&s.fields);
145 let field_name = match s.ctor_kind {
146 Some(CtorKind::Fn) => Some("Tuple Fields"),
147 None => Some("Fields"),
148 _ => None,
149 };
150 let mut items = vec![];
151 if let Some(name) = field_name {
152 items.push(LinkBlock::new(Link::new("fields", name), fields));
153 }
154 sidebar_assoc_items(cx, it, &mut items);
155 items
156 }
157
158 fn sidebar_trait<'a>(
159 cx: &'a Context<'_>,
160 it: &'a clean::Item,
161 t: &'a clean::Trait,
162 ) -> Vec<LinkBlock<'a>> {
163 fn filter_items<'a>(
164 items: &'a [clean::Item],
165 filt: impl Fn(&clean::Item) -> bool,
166 ty: &str,
167 ) -> Vec<Link<'a>> {
168 let mut res = items
169 .iter()
170 .filter_map(|m: &clean::Item| match m.name {
171 Some(ref name) if filt(m) => Some(Link::new(format!("{ty}.{name}"), name.as_str())),
172 _ => None,
173 })
174 .collect::<Vec<Link<'a>>>();
175 res.sort();
176 res
177 }
178
179 let req_assoc = filter_items(&t.items, |m| m.is_ty_associated_type(), "associatedtype");
180 let prov_assoc = filter_items(&t.items, |m| m.is_associated_type(), "associatedtype");
181 let req_assoc_const =
182 filter_items(&t.items, |m| m.is_ty_associated_const(), "associatedconstant");
183 let prov_assoc_const =
184 filter_items(&t.items, |m| m.is_associated_const(), "associatedconstant");
185 let req_method = filter_items(&t.items, |m| m.is_ty_method(), "tymethod");
186 let prov_method = filter_items(&t.items, |m| m.is_method(), "method");
187 let mut foreign_impls = vec![];
188 if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) {
189 foreign_impls.extend(
190 implementors
191 .iter()
192 .filter(|i| !i.is_on_local_type(cx))
193 .filter_map(|i| super::extract_for_impl_name(&i.impl_item, cx))
194 .map(|(name, id)| Link::new(id, name)),
195 );
196 foreign_impls.sort();
197 }
198
199 let mut blocks: Vec<LinkBlock<'_>> = [
200 ("required-associated-types", "Required Associated Types", req_assoc),
201 ("provided-associated-types", "Provided Associated Types", prov_assoc),
202 ("required-associated-consts", "Required Associated Constants", req_assoc_const),
203 ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const),
204 ("required-methods", "Required Methods", req_method),
205 ("provided-methods", "Provided Methods", prov_method),
206 ("foreign-impls", "Implementations on Foreign Types", foreign_impls),
207 ]
208 .into_iter()
209 .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), items))
210 .collect();
211 sidebar_assoc_items(cx, it, &mut blocks);
212 blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors")));
213 if t.is_auto(cx.tcx()) {
214 blocks.push(LinkBlock::forced(Link::new("synthetic-implementors", "Auto Implementors")));
215 }
216 blocks
217 }
218
219 fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
220 if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
221 let mut items = vec![];
222 sidebar_assoc_items(cx, it, &mut items);
223 items
224 } else {
225 let shared = Rc::clone(&cx.shared);
226 let (concrete, synthetic, blanket_impl) =
227 super::get_filtered_impls_for_reference(&shared, it);
228
229 sidebar_render_assoc_items(cx, &mut IdMap::new(), concrete, synthetic, blanket_impl).into()
230 }
231 }
232
233 fn sidebar_type_alias<'a>(
234 cx: &'a Context<'_>,
235 it: &'a clean::Item,
236 t: &'a clean::TypeAlias,
237 ) -> Vec<LinkBlock<'a>> {
238 let mut items = vec![];
239 if let Some(inner_type) = &t.inner_type {
240 items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type")));
241 match inner_type {
242 clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive: _ } => {
243 let mut variants = variants
244 .iter()
245 .filter(|i| !i.is_stripped())
246 .filter_map(|v| v.name)
247 .map(|name| Link::new(format!("variant.{name}"), name.to_string()))
248 .collect::<Vec<_>>();
249 variants.sort_unstable();
250
251 items.push(LinkBlock::new(Link::new("variants", "Variants"), variants));
252 }
253 clean::TypeAliasInnerType::Union { fields }
254 | clean::TypeAliasInnerType::Struct { ctor_kind: _, fields } => {
255 let fields = get_struct_fields_name(fields);
256 items.push(LinkBlock::new(Link::new("fields", "Fields"), fields));
257 }
258 }
259 }
260 sidebar_assoc_items(cx, it, &mut items);
261 items
262 }
263
264 fn sidebar_union<'a>(
265 cx: &'a Context<'_>,
266 it: &'a clean::Item,
267 u: &'a clean::Union,
268 ) -> Vec<LinkBlock<'a>> {
269 let fields = get_struct_fields_name(&u.fields);
270 let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)];
271 sidebar_assoc_items(cx, it, &mut items);
272 items
273 }
274
275 /// Adds trait implementations into the blocks of links
276 fn sidebar_assoc_items<'a>(
277 cx: &'a Context<'_>,
278 it: &'a clean::Item,
279 links: &mut Vec<LinkBlock<'a>>,
280 ) {
281 let did = it.item_id.expect_def_id();
282 let v = cx.shared.all_impls_for_item(it, it.item_id.expect_def_id());
283 let v = v.as_slice();
284
285 let mut assoc_consts = Vec::new();
286 let mut methods = Vec::new();
287 if !v.is_empty() {
288 let mut used_links = FxHashSet::default();
289 let mut id_map = IdMap::new();
290
291 {
292 let used_links_bor = &mut used_links;
293 assoc_consts.extend(
294 v.iter()
295 .filter(|i| i.inner_impl().trait_.is_none())
296 .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)),
297 );
298 // We want links' order to be reproducible so we don't use unstable sort.
299 assoc_consts.sort();
300
301 #[rustfmt::skip] // rustfmt makes the pipeline less readable
302 methods.extend(
303 v.iter()
304 .filter(|i| i.inner_impl().trait_.is_none())
305 .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())),
306 );
307
308 // We want links' order to be reproducible so we don't use unstable sort.
309 methods.sort();
310 }
311
312 let mut deref_methods = Vec::new();
313 let [concrete, synthetic, blanket] = if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
314 if let Some(impl_) =
315 v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
316 {
317 let mut derefs = DefIdSet::default();
318 derefs.insert(did);
319 sidebar_deref_methods(
320 cx,
321 &mut deref_methods,
322 impl_,
323 v.iter().copied(),
324 &mut derefs,
325 &mut used_links,
326 );
327 }
328
329 let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
330 v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
331 let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
332 concrete.into_iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_blanket());
333
334 sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl)
335 } else {
336 std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![]))
337 };
338
339 let mut blocks = vec![
340 LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts),
341 LinkBlock::new(Link::new("implementations", "Methods"), methods),
342 ];
343 blocks.append(&mut deref_methods);
344 blocks.extend([concrete, synthetic, blanket]);
345 links.append(&mut blocks);
346 }
347 }
348
349 fn sidebar_deref_methods<'a>(
350 cx: &'a Context<'_>,
351 out: &mut Vec<LinkBlock<'a>>,
352 impl_: &Impl,
353 v: impl Iterator<Item = &'a Impl>,
354 derefs: &mut DefIdSet,
355 used_links: &mut FxHashSet<String>,
356 ) {
357 let c = cx.cache();
358
359 debug!("found Deref: {impl_:?}");
360 if let Some((target, real_target)) =
361 impl_.inner_impl().items.iter().find_map(|item| match *item.kind {
362 clean::AssocTypeItem(box ref t, _) => Some(match *t {
363 clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_),
364 _ => (&t.type_, &t.type_),
365 }),
366 _ => None,
367 })
368 {
369 debug!("found target, real_target: {target:?} {real_target:?}");
370 if let Some(did) = target.def_id(c) &&
371 let Some(type_did) = impl_.inner_impl().for_.def_id(c) &&
372 // `impl Deref<Target = S> for S`
373 (did == type_did || !derefs.insert(did))
374 {
375 // Avoid infinite cycles
376 return;
377 }
378 let deref_mut = { v }.any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
379 let inner_impl = target
380 .def_id(c)
381 .or_else(|| {
382 target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned())
383 })
384 .and_then(|did| c.impls.get(&did));
385 if let Some(impls) = inner_impl {
386 debug!("found inner_impl: {impls:?}");
387 let mut ret = impls
388 .iter()
389 .filter(|i| i.inner_impl().trait_.is_none())
390 .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx()))
391 .collect::<Vec<_>>();
392 if !ret.is_empty() {
393 let id = if let Some(target_def_id) = real_target.def_id(c) {
394 Cow::Borrowed(
395 cx.deref_id_map
396 .get(&target_def_id)
397 .expect("Deref section without derived id")
398 .as_str(),
399 )
400 } else {
401 Cow::Borrowed("deref-methods")
402 };
403 let title = format!(
404 "Methods from {:#}<Target={:#}>",
405 impl_.inner_impl().trait_.as_ref().unwrap().print(cx),
406 real_target.print(cx),
407 );
408 // We want links' order to be reproducible so we don't use unstable sort.
409 ret.sort();
410 out.push(LinkBlock::new(Link::new(id, title), ret));
411 }
412 }
413
414 // Recurse into any further impls that might exist for `target`
415 if let Some(target_did) = target.def_id(c) &&
416 let Some(target_impls) = c.impls.get(&target_did) &&
417 let Some(target_deref_impl) = target_impls.iter().find(|i| {
418 i.inner_impl()
419 .trait_
420 .as_ref()
421 .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
422 .unwrap_or(false)
423 })
424 {
425 sidebar_deref_methods(
426 cx,
427 out,
428 target_deref_impl,
429 target_impls.iter(),
430 derefs,
431 used_links,
432 );
433 }
434 }
435 }
436
437 fn sidebar_enum<'a>(
438 cx: &'a Context<'_>,
439 it: &'a clean::Item,
440 e: &'a clean::Enum,
441 ) -> Vec<LinkBlock<'a>> {
442 let mut variants = e
443 .variants()
444 .filter_map(|v| v.name)
445 .map(|name| Link::new(format!("variant.{name}"), name.to_string()))
446 .collect::<Vec<_>>();
447 variants.sort_unstable();
448
449 let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)];
450 sidebar_assoc_items(cx, it, &mut items);
451 items
452 }
453
454 pub(crate) fn sidebar_module_like(
455 item_sections_in_use: FxHashSet<ItemSection>,
456 ) -> LinkBlock<'static> {
457 let item_sections = ItemSection::ALL
458 .iter()
459 .copied()
460 .filter(|sec| item_sections_in_use.contains(sec))
461 .map(|sec| Link::new(sec.id(), sec.name()))
462 .collect();
463 LinkBlock::new(Link::empty(), item_sections)
464 }
465
466 fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
467 let item_sections_in_use: FxHashSet<_> = items
468 .iter()
469 .filter(|it| {
470 !it.is_stripped()
471 && it
472 .name
473 .or_else(|| {
474 if let clean::ImportItem(ref i) = *it.kind &&
475 let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None }
476 })
477 .is_some()
478 })
479 .map(|it| item_ty_to_section(it.type_()))
480 .collect();
481
482 sidebar_module_like(item_sections_in_use)
483 }
484
485 fn sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
486 let mut items = vec![];
487 sidebar_assoc_items(cx, it, &mut items);
488 items
489 }
490
491 /// Renders the trait implementations for this type
492 fn sidebar_render_assoc_items(
493 cx: &Context<'_>,
494 id_map: &mut IdMap,
495 concrete: Vec<&Impl>,
496 synthetic: Vec<&Impl>,
497 blanket_impl: Vec<&Impl>,
498 ) -> [LinkBlock<'static>; 3] {
499 let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
500 let mut links = FxHashSet::default();
501
502 let mut ret = impls
503 .iter()
504 .filter_map(|it| {
505 let trait_ = it.inner_impl().trait_.as_ref()?;
506 let encoded =
507 id_map.derive(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx));
508
509 let prefix = match it.inner_impl().polarity {
510 ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
511 ty::ImplPolarity::Negative => "!",
512 };
513 let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx)));
514 if links.insert(generated.clone()) { Some(generated) } else { None }
515 })
516 .collect::<Vec<Link<'static>>>();
517 ret.sort();
518 ret
519 };
520
521 let concrete = format_impls(concrete, id_map);
522 let synthetic = format_impls(synthetic, id_map);
523 let blanket = format_impls(blanket_impl, id_map);
524 [
525 LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete),
526 LinkBlock::new(
527 Link::new("synthetic-implementations", "Auto Trait Implementations"),
528 synthetic,
529 ),
530 LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket),
531 ]
532 }
533
534 fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
535 if used_links.insert(url.clone()) {
536 return url;
537 }
538 let mut add = 1;
539 while !used_links.insert(format!("{url}-{add}")) {
540 add += 1;
541 }
542 format!("{url}-{add}")
543 }
544
545 fn get_methods<'a>(
546 i: &'a clean::Impl,
547 for_deref: bool,
548 used_links: &mut FxHashSet<String>,
549 deref_mut: bool,
550 tcx: TyCtxt<'_>,
551 ) -> Vec<Link<'a>> {
552 i.items
553 .iter()
554 .filter_map(|item| match item.name {
555 Some(ref name) if !name.is_empty() && item.is_method() => {
556 if !for_deref || super::should_render_item(item, deref_mut, tcx) {
557 Some(Link::new(
558 get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::Method)),
559 name.as_str(),
560 ))
561 } else {
562 None
563 }
564 }
565 _ => None,
566 })
567 .collect::<Vec<_>>()
568 }
569
570 fn get_associated_constants<'a>(
571 i: &'a clean::Impl,
572 used_links: &mut FxHashSet<String>,
573 ) -> Vec<Link<'a>> {
574 i.items
575 .iter()
576 .filter_map(|item| match item.name {
577 Some(ref name) if !name.is_empty() && item.is_associated_const() => Some(Link::new(
578 get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::AssocConst)),
579 name.as_str(),
580 )),
581 _ => None,
582 })
583 .collect::<Vec<_>>()
584 }