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.
should_render_blocks(&self) -> bool30 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> {
new(heading: Link<'a>, links: Vec<Link<'a>>) -> Self47 pub fn new(heading: Link<'a>, links: Vec<Link<'a>>) -> Self {
48 Self { heading, links, force_render: false }
49 }
50
forced(heading: Link<'a>) -> Self51 pub fn forced(heading: Link<'a>) -> Self {
52 Self { heading, links: vec![], force_render: true }
53 }
54
should_render(&self) -> bool55 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> {
new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self70 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 }
empty() -> Link<'static>73 pub fn empty() -> Link<'static> {
74 Link::new("", "")
75 }
76 }
77
print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer)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::TypedefItem(_) => sidebar_typedef(cx, it),
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_typedef()
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
get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>>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
sidebar_struct<'a>( cx: &'a Context<'_>, it: &'a clean::Item, s: &'a clean::Struct, ) -> Vec<LinkBlock<'a>>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
sidebar_trait<'a>( cx: &'a Context<'_>, it: &'a clean::Item, t: &'a clean::Trait, ) -> Vec<LinkBlock<'a>>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
sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>>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
sidebar_typedef<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>>233 fn sidebar_typedef<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
234 let mut items = vec![];
235 sidebar_assoc_items(cx, it, &mut items);
236 items
237 }
238
sidebar_union<'a>( cx: &'a Context<'_>, it: &'a clean::Item, u: &'a clean::Union, ) -> Vec<LinkBlock<'a>>239 fn sidebar_union<'a>(
240 cx: &'a Context<'_>,
241 it: &'a clean::Item,
242 u: &'a clean::Union,
243 ) -> Vec<LinkBlock<'a>> {
244 let fields = get_struct_fields_name(&u.fields);
245 let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)];
246 sidebar_assoc_items(cx, it, &mut items);
247 items
248 }
249
250 /// Adds trait implementations into the blocks of links
sidebar_assoc_items<'a>( cx: &'a Context<'_>, it: &'a clean::Item, links: &mut Vec<LinkBlock<'a>>, )251 fn sidebar_assoc_items<'a>(
252 cx: &'a Context<'_>,
253 it: &'a clean::Item,
254 links: &mut Vec<LinkBlock<'a>>,
255 ) {
256 let did = it.item_id.expect_def_id();
257 let cache = cx.cache();
258
259 let mut assoc_consts = Vec::new();
260 let mut methods = Vec::new();
261 if let Some(v) = cache.impls.get(&did) {
262 let mut used_links = FxHashSet::default();
263 let mut id_map = IdMap::new();
264
265 {
266 let used_links_bor = &mut used_links;
267 assoc_consts.extend(
268 v.iter()
269 .filter(|i| i.inner_impl().trait_.is_none())
270 .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)),
271 );
272 // We want links' order to be reproducible so we don't use unstable sort.
273 assoc_consts.sort();
274
275 #[rustfmt::skip] // rustfmt makes the pipeline less readable
276 methods.extend(
277 v.iter()
278 .filter(|i| i.inner_impl().trait_.is_none())
279 .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())),
280 );
281
282 // We want links' order to be reproducible so we don't use unstable sort.
283 methods.sort();
284 }
285
286 let mut deref_methods = Vec::new();
287 let [concrete, synthetic, blanket] = if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
288 if let Some(impl_) =
289 v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
290 {
291 let mut derefs = DefIdSet::default();
292 derefs.insert(did);
293 sidebar_deref_methods(
294 cx,
295 &mut deref_methods,
296 impl_,
297 v,
298 &mut derefs,
299 &mut used_links,
300 );
301 }
302
303 let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
304 v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
305 let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
306 concrete.into_iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_blanket());
307
308 sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl)
309 } else {
310 std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![]))
311 };
312
313 let mut blocks = vec![
314 LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts),
315 LinkBlock::new(Link::new("implementations", "Methods"), methods),
316 ];
317 blocks.append(&mut deref_methods);
318 blocks.extend([concrete, synthetic, blanket]);
319 links.append(&mut blocks);
320 }
321 }
322
sidebar_deref_methods<'a>( cx: &'a Context<'_>, out: &mut Vec<LinkBlock<'a>>, impl_: &Impl, v: &[Impl], derefs: &mut DefIdSet, used_links: &mut FxHashSet<String>, )323 fn sidebar_deref_methods<'a>(
324 cx: &'a Context<'_>,
325 out: &mut Vec<LinkBlock<'a>>,
326 impl_: &Impl,
327 v: &[Impl],
328 derefs: &mut DefIdSet,
329 used_links: &mut FxHashSet<String>,
330 ) {
331 let c = cx.cache();
332
333 debug!("found Deref: {:?}", impl_);
334 if let Some((target, real_target)) =
335 impl_.inner_impl().items.iter().find_map(|item| match *item.kind {
336 clean::AssocTypeItem(box ref t, _) => Some(match *t {
337 clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
338 _ => (&t.type_, &t.type_),
339 }),
340 _ => None,
341 })
342 {
343 debug!("found target, real_target: {:?} {:?}", target, real_target);
344 if let Some(did) = target.def_id(c) &&
345 let Some(type_did) = impl_.inner_impl().for_.def_id(c) &&
346 // `impl Deref<Target = S> for S`
347 (did == type_did || !derefs.insert(did))
348 {
349 // Avoid infinite cycles
350 return;
351 }
352 let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
353 let inner_impl = target
354 .def_id(c)
355 .or_else(|| {
356 target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned())
357 })
358 .and_then(|did| c.impls.get(&did));
359 if let Some(impls) = inner_impl {
360 debug!("found inner_impl: {:?}", impls);
361 let mut ret = impls
362 .iter()
363 .filter(|i| i.inner_impl().trait_.is_none())
364 .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx()))
365 .collect::<Vec<_>>();
366 if !ret.is_empty() {
367 let id = if let Some(target_def_id) = real_target.def_id(c) {
368 Cow::Borrowed(
369 cx.deref_id_map
370 .get(&target_def_id)
371 .expect("Deref section without derived id")
372 .as_str(),
373 )
374 } else {
375 Cow::Borrowed("deref-methods")
376 };
377 let title = format!(
378 "Methods from {:#}<Target={:#}>",
379 impl_.inner_impl().trait_.as_ref().unwrap().print(cx),
380 real_target.print(cx),
381 );
382 // We want links' order to be reproducible so we don't use unstable sort.
383 ret.sort();
384 out.push(LinkBlock::new(Link::new(id, title), ret));
385 }
386 }
387
388 // Recurse into any further impls that might exist for `target`
389 if let Some(target_did) = target.def_id(c) &&
390 let Some(target_impls) = c.impls.get(&target_did) &&
391 let Some(target_deref_impl) = target_impls.iter().find(|i| {
392 i.inner_impl()
393 .trait_
394 .as_ref()
395 .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
396 .unwrap_or(false)
397 })
398 {
399 sidebar_deref_methods(
400 cx,
401 out,
402 target_deref_impl,
403 target_impls,
404 derefs,
405 used_links,
406 );
407 }
408 }
409 }
410
sidebar_enum<'a>( cx: &'a Context<'_>, it: &'a clean::Item, e: &'a clean::Enum, ) -> Vec<LinkBlock<'a>>411 fn sidebar_enum<'a>(
412 cx: &'a Context<'_>,
413 it: &'a clean::Item,
414 e: &'a clean::Enum,
415 ) -> Vec<LinkBlock<'a>> {
416 let mut variants = e
417 .variants()
418 .filter_map(|v| v.name)
419 .map(|name| Link::new(format!("variant.{name}"), name.to_string()))
420 .collect::<Vec<_>>();
421 variants.sort_unstable();
422
423 let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)];
424 sidebar_assoc_items(cx, it, &mut items);
425 items
426 }
427
sidebar_module_like( item_sections_in_use: FxHashSet<ItemSection>, ) -> LinkBlock<'static>428 pub(crate) fn sidebar_module_like(
429 item_sections_in_use: FxHashSet<ItemSection>,
430 ) -> LinkBlock<'static> {
431 let item_sections = ItemSection::ALL
432 .iter()
433 .copied()
434 .filter(|sec| item_sections_in_use.contains(sec))
435 .map(|sec| Link::new(sec.id(), sec.name()))
436 .collect();
437 LinkBlock::new(Link::empty(), item_sections)
438 }
439
sidebar_module(items: &[clean::Item]) -> LinkBlock<'static>440 fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
441 let item_sections_in_use: FxHashSet<_> = items
442 .iter()
443 .filter(|it| {
444 !it.is_stripped()
445 && it
446 .name
447 .or_else(|| {
448 if let clean::ImportItem(ref i) = *it.kind &&
449 let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None }
450 })
451 .is_some()
452 })
453 .map(|it| item_ty_to_section(it.type_()))
454 .collect();
455
456 sidebar_module_like(item_sections_in_use)
457 }
458
sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>>459 fn sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
460 let mut items = vec![];
461 sidebar_assoc_items(cx, it, &mut items);
462 items
463 }
464
465 /// Renders the trait implementations for this type
sidebar_render_assoc_items( cx: &Context<'_>, id_map: &mut IdMap, concrete: Vec<&Impl>, synthetic: Vec<&Impl>, blanket_impl: Vec<&Impl>, ) -> [LinkBlock<'static>; 3]466 fn sidebar_render_assoc_items(
467 cx: &Context<'_>,
468 id_map: &mut IdMap,
469 concrete: Vec<&Impl>,
470 synthetic: Vec<&Impl>,
471 blanket_impl: Vec<&Impl>,
472 ) -> [LinkBlock<'static>; 3] {
473 let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
474 let mut links = FxHashSet::default();
475
476 let mut ret = impls
477 .iter()
478 .filter_map(|it| {
479 let trait_ = it.inner_impl().trait_.as_ref()?;
480 let encoded =
481 id_map.derive(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx));
482
483 let prefix = match it.inner_impl().polarity {
484 ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
485 ty::ImplPolarity::Negative => "!",
486 };
487 let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx)));
488 if links.insert(generated.clone()) { Some(generated) } else { None }
489 })
490 .collect::<Vec<Link<'static>>>();
491 ret.sort();
492 ret
493 };
494
495 let concrete = format_impls(concrete, id_map);
496 let synthetic = format_impls(synthetic, id_map);
497 let blanket = format_impls(blanket_impl, id_map);
498 [
499 LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete),
500 LinkBlock::new(
501 Link::new("synthetic-implementations", "Auto Trait Implementations"),
502 synthetic,
503 ),
504 LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket),
505 ]
506 }
507
get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String508 fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
509 if used_links.insert(url.clone()) {
510 return url;
511 }
512 let mut add = 1;
513 while !used_links.insert(format!("{}-{}", url, add)) {
514 add += 1;
515 }
516 format!("{}-{}", url, add)
517 }
518
get_methods<'a>( i: &'a clean::Impl, for_deref: bool, used_links: &mut FxHashSet<String>, deref_mut: bool, tcx: TyCtxt<'_>, ) -> Vec<Link<'a>>519 fn get_methods<'a>(
520 i: &'a clean::Impl,
521 for_deref: bool,
522 used_links: &mut FxHashSet<String>,
523 deref_mut: bool,
524 tcx: TyCtxt<'_>,
525 ) -> Vec<Link<'a>> {
526 i.items
527 .iter()
528 .filter_map(|item| match item.name {
529 Some(ref name) if !name.is_empty() && item.is_method() => {
530 if !for_deref || super::should_render_item(item, deref_mut, tcx) {
531 Some(Link::new(
532 get_next_url(used_links, format!("{}.{}", ItemType::Method, name)),
533 name.as_str(),
534 ))
535 } else {
536 None
537 }
538 }
539 _ => None,
540 })
541 .collect::<Vec<_>>()
542 }
543
get_associated_constants<'a>( i: &'a clean::Impl, used_links: &mut FxHashSet<String>, ) -> Vec<Link<'a>>544 fn get_associated_constants<'a>(
545 i: &'a clean::Impl,
546 used_links: &mut FxHashSet<String>,
547 ) -> Vec<Link<'a>> {
548 i.items
549 .iter()
550 .filter_map(|item| match item.name {
551 Some(ref name) if !name.is_empty() && item.is_associated_const() => Some(Link::new(
552 get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)),
553 name.as_str(),
554 )),
555 _ => None,
556 })
557 .collect::<Vec<_>>()
558 }
559