1use std::cell::RefCell;
17use std::ffi::OsString;
18use std::fs::File;
19use std::io::{self, Write as _};
20use std::iter::once;
21use std::marker::PhantomData;
22use std::path::{Component, Path, PathBuf};
23use std::rc::{Rc, Weak};
24use std::str::FromStr;
25use std::{fmt, fs};
26
27use indexmap::IndexMap;
28use itertools::Itertools;
29use regex::Regex;
30use rustc_data_structures::flock;
31use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
32use rustc_middle::ty::TyCtxt;
33use rustc_middle::ty::fast_reject::DeepRejectCtxt;
34use rustc_span::Symbol;
35use rustc_span::def_id::DefId;
36use serde::de::DeserializeOwned;
37use serde::ser::SerializeSeq;
38use serde::{Deserialize, Serialize, Serializer};
39
40use super::{Context, RenderMode, collect_paths_for_type, ensure_trailing_slash};
41use crate::clean::{Crate, Item, ItemId, ItemKind};
42use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
43use crate::docfs::PathError;
44use crate::error::Error;
45use crate::formats::Impl;
46use crate::formats::item_type::ItemType;
47use crate::html::layout;
48use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
49use crate::html::render::search_index::{SerializedSearchIndex, build_index};
50use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
51use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
52use crate::html::static_files::{self, suffix_path};
53use crate::visit::DocVisitor;
54use crate::{try_err, try_none};
55
56pub(crate) fn write_shared(
57 cx: &mut Context<'_>,
58 krate: &Crate,
59 opt: &RenderOptions,
60 tcx: TyCtxt<'_>,
61) -> Result<(), Error> {
62 cx.shared.fs.set_sync_only(true);
64 let lock_file = cx.dst.join(".lock");
65 let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
67
68 let SerializedSearchIndex { index, desc } = build_index(krate, &mut cx.shared.cache, tcx);
69 write_search_desc(cx, krate, &desc)?; let crate_name = krate.name(cx.tcx());
72 let crate_name = crate_name.as_str(); let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
75 let info = CrateInfo {
76 version: CrateInfoVersion::V1,
77 src_files_js: SourcesPart::get(cx, &crate_name_json)?,
78 search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
79 all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
80 crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
81 trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
82 type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
83 };
84
85 if let Some(parts_out_dir) = &opt.parts_out_dir {
86 create_parents(&parts_out_dir.0)?;
87 try_err!(
88 fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
89 &parts_out_dir.0
90 );
91 }
92
93 let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
94 crates.push(info);
95
96 if opt.should_merge.write_rendered_cci {
97 write_not_crate_specific(
98 &crates,
99 &cx.dst,
100 opt,
101 &cx.shared.style_files,
102 cx.shared.layout.css_file_extension.as_deref(),
103 &cx.shared.resource_suffix,
104 cx.info.include_sources,
105 )?;
106 match &opt.index_page {
107 Some(index_page) if opt.enable_index_page => {
108 let mut md_opts = opt.clone();
109 md_opts.output = cx.dst.clone();
110 md_opts.external_html = cx.shared.layout.external_html.clone();
111 try_err!(
112 crate::markdown::render_and_write(index_page, md_opts, cx.shared.edition()),
113 &index_page
114 );
115 }
116 None if opt.enable_index_page => {
117 write_rendered_cci::<CratesIndexPart, _>(
118 || CratesIndexPart::blank(cx),
119 &cx.dst,
120 &crates,
121 &opt.should_merge,
122 )?;
123 }
124 _ => {} }
126 }
127
128 cx.shared.fs.set_sync_only(false);
129 Ok(())
130}
131
132pub(crate) fn write_not_crate_specific(
136 crates: &[CrateInfo],
137 dst: &Path,
138 opt: &RenderOptions,
139 style_files: &[StylePath],
140 css_file_extension: Option<&Path>,
141 resource_suffix: &str,
142 include_sources: bool,
143) -> Result<(), Error> {
144 write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
145 write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
146 Ok(())
147}
148
149fn write_rendered_cross_crate_info(
150 crates: &[CrateInfo],
151 dst: &Path,
152 opt: &RenderOptions,
153 include_sources: bool,
154) -> Result<(), Error> {
155 let m = &opt.should_merge;
156 if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
157 if include_sources {
158 write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
159 }
160 write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, crates, m)?;
161 write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
162 }
163 write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
164 write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, crates, m)?;
165 Ok(())
166}
167
168fn write_static_files(
171 dst: &Path,
172 opt: &RenderOptions,
173 style_files: &[StylePath],
174 css_file_extension: Option<&Path>,
175 resource_suffix: &str,
176) -> Result<(), Error> {
177 let static_dir = dst.join("static.files");
178 try_err!(fs::create_dir_all(&static_dir), &static_dir);
179
180 for entry in style_files {
182 let theme = entry.basename()?;
183 let extension =
184 try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
185
186 if matches!(theme.as_str(), "light" | "dark" | "ayu") {
188 continue;
189 }
190
191 let bytes = try_err!(fs::read(&entry.path), &entry.path);
192 let filename = format!("{theme}{resource_suffix}.{extension}");
193 let dst_filename = dst.join(filename);
194 try_err!(fs::write(&dst_filename, bytes), &dst_filename);
195 }
196
197 if let Some(css) = css_file_extension {
200 let buffer = try_err!(fs::read_to_string(css), css);
201 let path = static_files::suffix_path("theme.css", resource_suffix);
202 let dst_path = dst.join(path);
203 try_err!(fs::write(&dst_path, buffer), &dst_path);
204 }
205
206 if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
207 static_files::for_each(|f: &static_files::StaticFile| {
208 let filename = static_dir.join(f.output_filename());
209 let contents: &[u8] =
210 if opt.disable_minification { f.src_bytes } else { f.minified_bytes };
211 fs::write(&filename, contents).map_err(|e| PathError::new(e, &filename))
212 })?;
213 }
214
215 Ok(())
216}
217
218fn write_search_desc(
220 cx: &mut Context<'_>,
221 krate: &Crate,
222 search_desc: &[(usize, String)],
223) -> Result<(), Error> {
224 let crate_name = krate.name(cx.tcx()).to_string();
225 let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap();
226 let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]);
227 if path.exists() {
228 try_err!(fs::remove_dir_all(&path), &path);
229 }
230 for (i, (_, part)) in search_desc.iter().enumerate() {
231 let filename = static_files::suffix_path(
232 &format!("{crate_name}-desc-{i}-.js"),
233 &cx.shared.resource_suffix,
234 );
235 let path = path.join(filename);
236 let part = OrderedJson::serialize(part).unwrap();
237 let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})");
238 create_parents(&path)?;
239 try_err!(fs::write(&path, part), &path);
240 }
241 Ok(())
242}
243
244#[derive(Serialize, Deserialize, Clone, Debug)]
246pub(crate) struct CrateInfo {
247 version: CrateInfoVersion,
248 src_files_js: PartsAndLocations<SourcesPart>,
249 search_index_js: PartsAndLocations<SearchIndexPart>,
250 all_crates: PartsAndLocations<AllCratesPart>,
251 crates_index: PartsAndLocations<CratesIndexPart>,
252 trait_impl: PartsAndLocations<TraitAliasPart>,
253 type_impl: PartsAndLocations<TypeAliasPart>,
254}
255
256impl CrateInfo {
257 pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
259 parts_paths
260 .iter()
261 .map(|parts_path| {
262 let path = &parts_path.0;
263 let parts = try_err!(fs::read(path), &path);
264 let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
265 Ok::<_, Error>(parts)
266 })
267 .collect::<Result<Vec<CrateInfo>, Error>>()
268 }
269}
270
271#[derive(Serialize, Deserialize, Clone, Debug)]
279enum CrateInfoVersion {
280 V1,
281}
282
283#[derive(Serialize, Deserialize, Debug, Clone)]
285#[serde(transparent)]
286struct PartsAndLocations<P> {
287 parts: Vec<(PathBuf, P)>,
288}
289
290impl<P> Default for PartsAndLocations<P> {
291 fn default() -> Self {
292 Self { parts: Vec::default() }
293 }
294}
295
296impl<T, U> PartsAndLocations<Part<T, U>> {
297 fn push(&mut self, path: PathBuf, item: U) {
298 self.parts.push((path, Part { _artifact: PhantomData, item }));
299 }
300
301 fn with(path: PathBuf, part: U) -> Self {
303 let mut ret = Self::default();
304 ret.push(path, part);
305 ret
306 }
307}
308
309#[derive(Serialize, Deserialize, Debug, Clone)]
313#[serde(transparent)]
314struct Part<T, U> {
315 #[serde(skip)]
316 _artifact: PhantomData<T>,
317 item: U,
318}
319
320impl<T, U: fmt::Display> fmt::Display for Part<T, U> {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 write!(f, "{}", self.item)
324 }
325}
326
327trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
329 type FileFormat: sorted_template::FileFormat;
331 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
332}
333
334#[derive(Serialize, Deserialize, Clone, Default, Debug)]
335struct SearchIndex;
336type SearchIndexPart = Part<SearchIndex, EscapedJson>;
337impl CciPart for SearchIndexPart {
338 type FileFormat = sorted_template::Js;
339 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
340 &crate_info.search_index_js
341 }
342}
343
344impl SearchIndexPart {
345 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
346 SortedTemplate::from_before_after(
347 r"var searchIndex = new Map(JSON.parse('[",
348 r"]'));
349if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
350else if (window.initSearch) window.initSearch(searchIndex);",
351 )
352 }
353
354 fn get(
355 search_index: OrderedJson,
356 resource_suffix: &str,
357 ) -> Result<PartsAndLocations<Self>, Error> {
358 let path = suffix_path("search-index.js", resource_suffix);
359 let search_index = EscapedJson::from(search_index);
360 Ok(PartsAndLocations::with(path, search_index))
361 }
362}
363
364#[derive(Serialize, Deserialize, Clone, Default, Debug)]
365struct AllCrates;
366type AllCratesPart = Part<AllCrates, OrderedJson>;
367impl CciPart for AllCratesPart {
368 type FileFormat = sorted_template::Js;
369 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
370 &crate_info.all_crates
371 }
372}
373
374impl AllCratesPart {
375 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
376 SortedTemplate::from_before_after("window.ALL_CRATES = [", "];")
377 }
378
379 fn get(
380 crate_name_json: OrderedJson,
381 resource_suffix: &str,
382 ) -> Result<PartsAndLocations<Self>, Error> {
383 let path = suffix_path("crates.js", resource_suffix);
386 Ok(PartsAndLocations::with(path, crate_name_json))
387 }
388}
389
390fn hack_get_external_crate_names(
397 doc_root: &Path,
398 resource_suffix: &str,
399) -> Result<Vec<String>, Error> {
400 let path = doc_root.join(suffix_path("crates.js", resource_suffix));
401 let Ok(content) = fs::read_to_string(&path) else {
402 return Ok(Vec::default());
404 };
405 let regex = Regex::new(r"\[.*\]").unwrap();
408 let Some(content) = regex.find(&content) else {
409 return Err(Error::new("could not find crates list in crates.js", path));
410 };
411 let content: Vec<String> = try_err!(serde_json::from_str(content.as_str()), &path);
412 Ok(content)
413}
414
415#[derive(Serialize, Deserialize, Clone, Default, Debug)]
416struct CratesIndex;
417type CratesIndexPart = Part<CratesIndex, String>;
418impl CciPart for CratesIndexPart {
419 type FileFormat = sorted_template::Html;
420 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
421 &crate_info.crates_index
422 }
423}
424
425impl CratesIndexPart {
426 fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
427 let page = layout::Page {
428 title: "Index of crates",
429 css_class: "mod sys",
430 root_path: "./",
431 static_root_path: cx.shared.static_root_path.as_deref(),
432 description: "List of crates",
433 resource_suffix: &cx.shared.resource_suffix,
434 rust_logo: true,
435 };
436 let layout = &cx.shared.layout;
437 let style_files = &cx.shared.style_files;
438 const DELIMITER: &str = "\u{FFFC}"; let content =
440 format!("<h1>List of all crates</h1><ul class=\"all-items\">{DELIMITER}</ul>");
441 let template = layout::render(layout, &page, "", content, style_files);
442 match SortedTemplate::from_template(&template, DELIMITER) {
443 Ok(template) => template,
444 Err(e) => panic!(
445 "Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}"
446 ),
447 }
448 }
449
450 fn get(crate_name: &str, external_crates: &[String]) -> Result<PartsAndLocations<Self>, Error> {
452 let mut ret = PartsAndLocations::default();
453 let path = PathBuf::from("index.html");
454 for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) {
455 let part = format!(
456 "<li><a href=\"{trailing_slash}index.html\">{crate_name}</a></li>",
457 trailing_slash = ensure_trailing_slash(crate_name),
458 );
459 ret.push(path.clone(), part);
460 }
461 Ok(ret)
462 }
463}
464
465#[derive(Serialize, Deserialize, Clone, Default, Debug)]
466struct Sources;
467type SourcesPart = Part<Sources, EscapedJson>;
468impl CciPart for SourcesPart {
469 type FileFormat = sorted_template::Js;
470 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
471 &crate_info.src_files_js
472 }
473}
474
475impl SourcesPart {
476 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
477 SortedTemplate::from_before_after(
481 r"var srcIndex = new Map(JSON.parse('[",
482 r"]'));
483createSrcSidebar();",
484 )
485 }
486
487 fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result<PartsAndLocations<Self>, Error> {
488 let hierarchy = Rc::new(Hierarchy::default());
489 cx.shared
490 .local_sources
491 .iter()
492 .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
493 .for_each(|source| hierarchy.add_path(source));
494 let path = suffix_path("src-files.js", &cx.shared.resource_suffix);
495 let hierarchy = hierarchy.to_json_string();
496 let part = OrderedJson::array_unsorted([crate_name, &hierarchy]);
497 let part = EscapedJson::from(part);
498 Ok(PartsAndLocations::with(path, part))
499 }
500}
501
502#[derive(Debug, Default)]
504struct Hierarchy {
505 parent: Weak<Self>,
506 elem: OsString,
507 children: RefCell<FxIndexMap<OsString, Rc<Self>>>,
508 elems: RefCell<FxIndexSet<OsString>>,
509}
510
511impl Hierarchy {
512 fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
513 Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
514 }
515
516 fn to_json_string(&self) -> OrderedJson {
517 let subs = self.children.borrow();
518 let files = self.elems.borrow();
519 let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion"))
520 .unwrap();
521 let mut out = Vec::from([name]);
522 if !subs.is_empty() || !files.is_empty() {
523 let subs = subs.iter().map(|(_, s)| s.to_json_string());
524 out.push(OrderedJson::array_sorted(subs));
525 }
526 if !files.is_empty() {
527 let files = files
528 .iter()
529 .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap());
530 out.push(OrderedJson::array_sorted(files));
531 }
532 OrderedJson::array_unsorted(out)
533 }
534
535 fn add_path(self: &Rc<Self>, path: &Path) {
536 let mut h = Rc::clone(self);
537 let mut elems = path
538 .components()
539 .filter_map(|s| match s {
540 Component::Normal(s) => Some(s.to_owned()),
541 Component::ParentDir => Some(OsString::from("..")),
542 _ => None,
543 })
544 .peekable();
545 loop {
546 let cur_elem = elems.next().expect("empty file path");
547 if cur_elem == ".." {
548 if let Some(parent) = h.parent.upgrade() {
549 h = parent;
550 }
551 continue;
552 }
553 if elems.peek().is_none() {
554 h.elems.borrow_mut().insert(cur_elem);
555 break;
556 } else {
557 let entry = Rc::clone(
558 h.children
559 .borrow_mut()
560 .entry(cur_elem.clone())
561 .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))),
562 );
563 h = entry;
564 }
565 }
566 }
567}
568
569#[derive(Serialize, Deserialize, Clone, Default, Debug)]
570struct TypeAlias;
571type TypeAliasPart = Part<TypeAlias, OrderedJson>;
572impl CciPart for TypeAliasPart {
573 type FileFormat = sorted_template::Js;
574 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
575 &crate_info.type_impl
576 }
577}
578
579impl TypeAliasPart {
580 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
581 SortedTemplate::from_before_after(
582 r"(function() {
583 var type_impls = Object.fromEntries([",
584 r"]);
585 if (window.register_type_impls) {
586 window.register_type_impls(type_impls);
587 } else {
588 window.pending_type_impls = type_impls;
589 }
590})()",
591 )
592 }
593
594 fn get(
595 cx: &mut Context<'_>,
596 krate: &Crate,
597 crate_name_json: &OrderedJson,
598 ) -> Result<PartsAndLocations<Self>, Error> {
599 let mut path_parts = PartsAndLocations::default();
600
601 let mut type_impl_collector = TypeImplCollector {
602 aliased_types: IndexMap::default(),
603 visited_aliases: FxHashSet::default(),
604 cx,
605 };
606 DocVisitor::visit_crate(&mut type_impl_collector, krate);
607 let cx = type_impl_collector.cx;
608 let aliased_types = type_impl_collector.aliased_types;
609 for aliased_type in aliased_types.values() {
610 let impls = aliased_type
611 .impl_
612 .values()
613 .flat_map(|AliasedTypeImpl { impl_, type_aliases }| {
614 let mut ret = Vec::new();
615 let trait_ = impl_
616 .inner_impl()
617 .trait_
618 .as_ref()
619 .map(|trait_| format!("{:#}", trait_.print(cx)));
620 for &(type_alias_fqp, type_alias_item) in type_aliases {
624 cx.id_map.borrow_mut().clear();
625 cx.deref_id_map.borrow_mut().clear();
626 let target_did = impl_
627 .inner_impl()
628 .trait_
629 .as_ref()
630 .map(|trait_| trait_.def_id())
631 .or_else(|| impl_.inner_impl().for_.def_id(&cx.shared.cache));
632 let provided_methods;
633 let assoc_link = if let Some(target_did) = target_did {
634 provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx());
635 AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods)
636 } else {
637 AssocItemLink::Anchor(None)
638 };
639 let text = {
640 let mut buf = String::new();
641 super::render_impl(
642 &mut buf,
643 cx,
644 impl_,
645 type_alias_item,
646 assoc_link,
647 RenderMode::Normal,
648 None,
649 &[],
650 ImplRenderingParameters {
651 show_def_docs: true,
652 show_default_items: true,
653 show_non_assoc_items: true,
654 toggle_open_by_default: true,
655 },
656 );
657 buf
658 };
659 let type_alias_fqp = (*type_alias_fqp).iter().join("::");
660 if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) {
661 ret.last_mut()
662 .expect("already established that ret.last() is Some()")
663 .aliases
664 .push(type_alias_fqp);
665 } else {
666 ret.push(AliasSerializableImpl {
667 text,
668 trait_: trait_.clone(),
669 aliases: vec![type_alias_fqp],
670 })
671 }
672 }
673 ret
674 })
675 .collect::<Vec<_>>();
676
677 let mut path = PathBuf::from("type.impl");
678 for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
679 path.push(component.as_str());
680 }
681 let aliased_item_type = aliased_type.target_type;
682 path.push(format!(
683 "{aliased_item_type}.{}.js",
684 aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
685 ));
686
687 let part = OrderedJson::array_sorted(
688 impls.iter().map(OrderedJson::serialize).collect::<Result<Vec<_>, _>>().unwrap(),
689 );
690 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
691 }
692 Ok(path_parts)
693 }
694}
695
696#[derive(Serialize, Deserialize, Clone, Default, Debug)]
697struct TraitAlias;
698type TraitAliasPart = Part<TraitAlias, OrderedJson>;
699impl CciPart for TraitAliasPart {
700 type FileFormat = sorted_template::Js;
701 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
702 &crate_info.trait_impl
703 }
704}
705
706impl TraitAliasPart {
707 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
708 SortedTemplate::from_before_after(
709 r"(function() {
710 var implementors = Object.fromEntries([",
711 r"]);
712 if (window.register_implementors) {
713 window.register_implementors(implementors);
714 } else {
715 window.pending_implementors = implementors;
716 }
717})()",
718 )
719 }
720
721 fn get(
722 cx: &Context<'_>,
723 crate_name_json: &OrderedJson,
724 ) -> Result<PartsAndLocations<Self>, Error> {
725 let cache = &cx.shared.cache;
726 let mut path_parts = PartsAndLocations::default();
727 for (&did, imps) in &cache.implementors {
730 let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
738 Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
739 Some((_, t)) => (p, t),
740 None => continue,
741 },
742 None => match cache.external_paths.get(&did) {
743 Some((p, t)) => (p, t),
744 None => continue,
745 },
746 };
747
748 let implementors = imps
749 .iter()
750 .filter_map(|imp| {
751 if imp.impl_item.item_id.krate() == did.krate
759 || !imp.impl_item.item_id.is_local()
760 {
761 None
762 } else {
763 Some(Implementor {
764 text: imp.inner_impl().print(false, cx).to_string(),
765 synthetic: imp.inner_impl().kind.is_auto(),
766 types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache),
767 })
768 }
769 })
770 .collect::<Vec<_>>();
771
772 if implementors.is_empty() && !cache.paths.contains_key(&did) {
776 continue;
777 }
778
779 let mut path = PathBuf::from("trait.impl");
780 for component in &remote_path[..remote_path.len() - 1] {
781 path.push(component.as_str());
782 }
783 path.push(format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
784
785 let part = OrderedJson::array_sorted(
786 implementors
787 .iter()
788 .map(OrderedJson::serialize)
789 .collect::<Result<Vec<_>, _>>()
790 .unwrap(),
791 );
792 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
793 }
794 Ok(path_parts)
795 }
796}
797
798struct Implementor {
799 text: String,
800 synthetic: bool,
801 types: Vec<String>,
802}
803
804impl Serialize for Implementor {
805 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
806 where
807 S: Serializer,
808 {
809 let mut seq = serializer.serialize_seq(None)?;
810 seq.serialize_element(&self.text)?;
811 if self.synthetic {
812 seq.serialize_element(&1)?;
813 seq.serialize_element(&self.types)?;
814 }
815 seq.end()
816 }
817}
818
819struct TypeImplCollector<'cx, 'cache, 'item> {
827 aliased_types: IndexMap<DefId, AliasedType<'cache, 'item>>,
829 visited_aliases: FxHashSet<DefId>,
830 cx: &'cache Context<'cx>,
831}
832
833struct AliasedType<'cache, 'item> {
849 target_fqp: &'cache [Symbol],
851 target_type: ItemType,
852 impl_: IndexMap<ItemId, AliasedTypeImpl<'cache, 'item>>,
855}
856
857struct AliasedTypeImpl<'cache, 'item> {
862 impl_: &'cache Impl,
863 type_aliases: Vec<(&'cache [Symbol], &'item Item)>,
864}
865
866impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> {
867 fn visit_item(&mut self, it: &'item Item) {
868 self.visit_item_recur(it);
869 let cache = &self.cx.shared.cache;
870 let ItemKind::TypeAliasItem(ref t) = it.kind else { return };
871 let Some(self_did) = it.item_id.as_def_id() else { return };
872 if !self.visited_aliases.insert(self_did) {
873 return;
874 }
875 let Some(target_did) = t.type_.def_id(cache) else { return };
876 let get_extern = { || cache.external_paths.get(&target_did) };
877 let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern)
878 else {
879 return;
880 };
881 let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
882 let impl_ = cache
883 .impls
884 .get(&target_did)
885 .map(|v| &v[..])
886 .unwrap_or_default()
887 .iter()
888 .map(|impl_| {
889 (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() })
890 })
891 .collect();
892 AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
893 });
894 let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) };
895 let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
896 return;
897 };
898 let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
899 let mut seen_impls: FxHashSet<ItemId> = cache
903 .impls
904 .get(&self_did)
905 .map(|s| &s[..])
906 .unwrap_or_default()
907 .iter()
908 .map(|i| i.impl_item.item_id)
909 .collect();
910 for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
911 let Some(impl_did) = impl_item_id.as_def_id() else { continue };
918 let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
919 let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx());
920 if !reject_cx.types_may_unify(aliased_ty, for_ty) {
921 continue;
922 }
923 if !seen_impls.insert(*impl_item_id) {
925 continue;
926 }
927 aliased_type_impl.type_aliases.push((&self_fqp[..], it));
929 }
930 }
931}
932
933struct AliasSerializableImpl {
935 text: String,
936 trait_: Option<String>,
937 aliases: Vec<String>,
938}
939
940impl Serialize for AliasSerializableImpl {
941 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
942 where
943 S: Serializer,
944 {
945 let mut seq = serializer.serialize_seq(None)?;
946 seq.serialize_element(&self.text)?;
947 if let Some(trait_) = &self.trait_ {
948 seq.serialize_element(trait_)?;
949 } else {
950 seq.serialize_element(&0)?;
951 }
952 for type_ in &self.aliases {
953 seq.serialize_element(type_)?;
954 }
955 seq.end()
956 }
957}
958
959fn get_path_parts<T: CciPart>(
960 dst: &Path,
961 crates_info: &[CrateInfo],
962) -> FxIndexMap<PathBuf, Vec<String>> {
963 let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default();
964 crates_info.iter().flat_map(|crate_info| T::from_crate_info(crate_info).parts.iter()).for_each(
965 |(path, part)| {
966 let path = dst.join(path);
967 let part = part.to_string();
968 templates.entry(path).or_default().push(part);
969 },
970 );
971 templates
972}
973
974fn create_parents(path: &Path) -> Result<(), Error> {
976 let parent = path.parent().expect("should not have an empty path here");
977 try_err!(fs::create_dir_all(parent), parent);
978 Ok(())
979}
980
981fn read_template_or_blank<F, T: FileFormat>(
983 mut make_blank: F,
984 path: &Path,
985 should_merge: &ShouldMerge,
986) -> Result<SortedTemplate<T>, Error>
987where
988 F: FnMut() -> SortedTemplate<T>,
989{
990 if !should_merge.read_rendered_cci {
991 return Ok(make_blank());
992 }
993 match fs::read_to_string(path) {
994 Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
995 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
996 Err(e) => Err(Error::new(e, path)),
997 }
998}
999
1000fn write_rendered_cci<T: CciPart, F>(
1002 mut make_blank: F,
1003 dst: &Path,
1004 crates_info: &[CrateInfo],
1005 should_merge: &ShouldMerge,
1006) -> Result<(), Error>
1007where
1008 F: FnMut() -> SortedTemplate<T::FileFormat>,
1009{
1010 for (path, parts) in get_path_parts::<T>(dst, crates_info) {
1012 create_parents(&path)?;
1013 let mut template =
1015 read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
1016 for part in parts {
1017 template.append(part);
1018 }
1019 let mut file = try_err!(File::create_buffered(&path), &path);
1020 try_err!(write!(file, "{template}"), &path);
1021 try_err!(file.flush(), &path);
1022 }
1023 Ok(())
1024}
1025
1026#[cfg(test)]
1027mod tests;