1use std::cell::RefCell;
17use std::ffi::{OsStr, 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 rustc_ast::join_path_syms;
29use rustc_data_structures::flock;
30use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
31use rustc_middle::ty::TyCtxt;
32use rustc_middle::ty::fast_reject::DeepRejectCtxt;
33use rustc_span::Symbol;
34use rustc_span::def_id::DefId;
35use serde::de::DeserializeOwned;
36use serde::ser::SerializeSeq;
37use serde::{Deserialize, Serialize, Serializer};
38
39use super::{Context, RenderMode, collect_paths_for_type, ensure_trailing_slash};
40use crate::clean::{Crate, Item, ItemId, ItemKind};
41use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
42use crate::docfs::PathError;
43use crate::error::Error;
44use crate::formats::Impl;
45use crate::formats::item_type::ItemType;
46use crate::html::format::{print_impl, print_path};
47use crate::html::layout;
48use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
49use crate::html::render::print_item::compare_names;
50use crate::html::render::search_index::{SerializedSearchIndex, build_index};
51use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
52use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
53use crate::html::static_files::{self, suffix_path};
54use crate::visit::DocVisitor;
55use crate::{try_err, try_none};
56
57pub(crate) fn write_shared(
58 cx: &mut Context<'_>,
59 krate: &Crate,
60 opt: &RenderOptions,
61 tcx: TyCtxt<'_>,
62) -> Result<(), Error> {
63 cx.shared.fs.set_sync_only(true);
65 let lock_file = cx.dst.join(".lock");
66 let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
68
69 let search_index = build_index(
70 krate,
71 &mut cx.shared.cache,
72 tcx,
73 &cx.dst,
74 &cx.shared.resource_suffix,
75 &opt.should_merge,
76 )?;
77
78 let crate_name = krate.name(cx.tcx());
79 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)?;
82 let info = CrateInfo {
83 version: CrateInfoVersion::V2,
84 src_files_js: SourcesPart::get(cx, &crate_name_json)?,
85 search_index,
86 all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
87 crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
88 trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
89 type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
90 };
91
92 if let Some(parts_out_dir) = &opt.parts_out_dir {
93 let mut parts_out_file = parts_out_dir.0.clone();
94 parts_out_file.push(&format!("{crate_name}.json"));
95 create_parents(&parts_out_file)?;
96 try_err!(
97 fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()),
98 &parts_out_dir.0
99 );
100 }
101
102 let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
103 crates.push(info);
104
105 if opt.should_merge.write_rendered_cci {
106 write_not_crate_specific(
107 &crates,
108 &cx.dst,
109 opt,
110 &cx.shared.style_files,
111 cx.shared.layout.css_file_extension.as_deref(),
112 &cx.shared.resource_suffix,
113 cx.info.include_sources,
114 )?;
115 match &opt.index_page {
116 Some(index_page) if opt.enable_index_page => {
117 let mut md_opts = opt.clone();
118 md_opts.output = cx.dst.clone();
119 md_opts.external_html = cx.shared.layout.external_html.clone();
120 let file = try_err!(cx.sess().source_map().load_file(&index_page), &index_page);
121 try_err!(
122 crate::markdown::render_and_write(file, md_opts, cx.shared.edition()),
123 &index_page
124 );
125 }
126 None if opt.enable_index_page => {
127 write_rendered_cci::<CratesIndexPart, _>(
128 || CratesIndexPart::blank(cx),
129 &cx.dst,
130 &crates,
131 &opt.should_merge,
132 )?;
133 }
134 _ => {} }
136 }
137
138 cx.shared.fs.set_sync_only(false);
139 Ok(())
140}
141
142pub(crate) fn write_not_crate_specific(
146 crates: &[CrateInfo],
147 dst: &Path,
148 opt: &RenderOptions,
149 style_files: &[StylePath],
150 css_file_extension: Option<&Path>,
151 resource_suffix: &str,
152 include_sources: bool,
153) -> Result<(), Error> {
154 write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
155 write_resources(dst, opt, style_files, css_file_extension, resource_suffix)?;
156 Ok(())
157}
158
159fn write_rendered_cross_crate_info(
160 crates: &[CrateInfo],
161 dst: &Path,
162 opt: &RenderOptions,
163 include_sources: bool,
164 resource_suffix: &str,
165) -> Result<(), Error> {
166 let m = &opt.should_merge;
167 if opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
168 if include_sources {
169 write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
170 }
171 crates
172 .iter()
173 .fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
174 .sort()
175 .write_to(dst, resource_suffix)?;
176 write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
177 }
178 write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
179 write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, crates, m)?;
180 Ok(())
181}
182
183fn write_resources(
186 dst: &Path,
187 opt: &RenderOptions,
188 style_files: &[StylePath],
189 css_file_extension: Option<&Path>,
190 resource_suffix: &str,
191) -> Result<(), Error> {
192 if opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
193 for entry in style_files {
195 let theme = entry.basename()?;
196 let extension =
197 try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
198
199 if matches!(theme.as_str(), "light" | "dark" | "ayu") {
201 continue;
202 }
203
204 let bytes = try_err!(fs::read(&entry.path), &entry.path);
205 let filename = format!("{theme}{resource_suffix}.{extension}");
206 let dst_filename = dst.join(filename);
207 try_err!(fs::write(&dst_filename, bytes), &dst_filename);
208 }
209
210 if let Some(css) = css_file_extension {
213 let buffer = try_err!(fs::read_to_string(css), css);
214 let path = static_files::suffix_path("theme.css", resource_suffix);
215 let dst_path = dst.join(path);
216 try_err!(fs::write(&dst_path, buffer), &dst_path);
217 }
218 }
219
220 if opt.emit.contains(&EmitType::HtmlStaticFiles) {
221 let static_dir = dst.join("static.files");
222 try_err!(fs::create_dir_all(&static_dir), &static_dir);
223
224 static_files::for_each(|f: &static_files::StaticFile| {
225 let filename = static_dir.join(f.output_filename());
226 let contents: &[u8] =
227 if opt.disable_minification { f.src_bytes } else { f.minified_bytes };
228 fs::write(&filename, contents).map_err(|e| PathError::new(e, &filename))
229 })?;
230 }
231
232 Ok(())
233}
234
235#[derive(Serialize, Deserialize, Clone, Debug)]
237pub(crate) struct CrateInfo {
238 version: CrateInfoVersion,
239 src_files_js: PartsAndLocations<SourcesPart>,
240 search_index: SerializedSearchIndex,
241 all_crates: PartsAndLocations<AllCratesPart>,
242 crates_index: PartsAndLocations<CratesIndexPart>,
243 trait_impl: PartsAndLocations<TraitAliasPart>,
244 type_impl: PartsAndLocations<TypeAliasPart>,
245}
246
247impl CrateInfo {
248 pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
250 parts_paths
251 .iter()
252 .fold(Ok(Vec::new()), |acc, parts_path| {
253 let mut acc = acc?;
254 let dir = &parts_path.0;
255 acc.append(&mut try_err!(std::fs::read_dir(dir), dir.as_path())
256 .filter_map(|file| {
257 let to_crate_info = |file: Result<std::fs::DirEntry, std::io::Error>| -> Result<Option<CrateInfo>, Error> {
258 let file = try_err!(file, dir.as_path());
259 if file.path().extension() != Some(OsStr::new("json")) {
260 return Ok(None);
261 }
262 let parts = try_err!(fs::read(file.path()), file.path());
263 let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), file.path());
264 Ok(Some(parts))
265 };
266 to_crate_info(file).transpose()
267 })
268 .collect::<Result<Vec<CrateInfo>, Error>>()?);
269 Ok(acc)
270 })
271 }
272}
273
274#[derive(Serialize, Deserialize, Clone, Debug)]
282enum CrateInfoVersion {
283 V2,
284}
285
286#[derive(Serialize, Deserialize, Debug, Clone)]
288#[serde(transparent)]
289struct PartsAndLocations<P> {
290 parts: Vec<(PathBuf, P)>,
291}
292
293impl<P> Default for PartsAndLocations<P> {
294 fn default() -> Self {
295 Self { parts: Vec::default() }
296 }
297}
298
299impl<T, U> PartsAndLocations<Part<T, U>> {
300 fn push(&mut self, path: PathBuf, item: U) {
301 self.parts.push((path, Part { _artifact: PhantomData, item }));
302 }
303
304 fn with(path: PathBuf, part: U) -> Self {
306 let mut ret = Self::default();
307 ret.push(path, part);
308 ret
309 }
310}
311
312#[derive(Serialize, Deserialize, Debug, Clone)]
316#[serde(transparent)]
317struct Part<T, U> {
318 #[serde(skip)]
319 _artifact: PhantomData<T>,
320 item: U,
321}
322
323impl<T, U: fmt::Display> fmt::Display for Part<T, U> {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 write!(f, "{}", self.item)
327 }
328}
329
330trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
332 type FileFormat: sorted_template::FileFormat;
334 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
335}
336
337#[derive(Serialize, Deserialize, Clone, Default, Debug)]
338struct AllCrates;
339type AllCratesPart = Part<AllCrates, OrderedJson>;
340impl CciPart for AllCratesPart {
341 type FileFormat = sorted_template::Js;
342 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
343 &crate_info.all_crates
344 }
345}
346
347impl AllCratesPart {
348 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
349 SortedTemplate::from_before_after("window.ALL_CRATES = [", "];")
350 }
351
352 fn get(
353 crate_name_json: OrderedJson,
354 resource_suffix: &str,
355 ) -> Result<PartsAndLocations<Self>, Error> {
356 let path = suffix_path("crates.js", resource_suffix);
359 Ok(PartsAndLocations::with(path, crate_name_json))
360 }
361}
362
363fn hack_get_external_crate_names(
370 doc_root: &Path,
371 resource_suffix: &str,
372) -> Result<Vec<String>, Error> {
373 let path = doc_root.join(suffix_path("crates.js", resource_suffix));
374 let Ok(content) = fs::read_to_string(&path) else {
375 return Ok(Vec::default());
377 };
378 if let Some(start) = content.find('[')
381 && let Some(end) = content[start..].find(']')
382 {
383 let content: Vec<String> =
384 try_err!(serde_json::from_str(&content[start..=start + end]), &path);
385 Ok(content)
386 } else {
387 Err(Error::new("could not find crates list in crates.js", path))
388 }
389}
390
391#[derive(Serialize, Deserialize, Clone, Default, Debug)]
392struct CratesIndex;
393type CratesIndexPart = Part<CratesIndex, String>;
394impl CciPart for CratesIndexPart {
395 type FileFormat = sorted_template::Html;
396 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
397 &crate_info.crates_index
398 }
399}
400
401impl CratesIndexPart {
402 fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
403 let page = layout::Page {
404 title: "Index of crates",
405 short_title: "Crates",
406 css_class: "mod sys",
407 root_path: "./",
408 static_root_path: cx.shared.static_root_path.as_deref(),
409 description: "List of crates",
410 resource_suffix: &cx.shared.resource_suffix,
411 rust_logo: true,
412 };
413 let layout = &cx.shared.layout;
414 let style_files = &cx.shared.style_files;
415 const DELIMITER: &str = "\u{FFFC}"; let content = format!(
417 "<div class=\"main-heading\">\
418 <h1>List of all crates</h1>\
419 <rustdoc-toolbar></rustdoc-toolbar>\
420 </div>\
421 <ul class=\"all-items\">{DELIMITER}</ul>"
422 );
423 let template = layout::render(layout, &page, "", content, style_files);
424 SortedTemplate::from_template(&template, DELIMITER)
425 .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page")
426 }
427
428 fn get(crate_name: &str, external_crates: &[String]) -> Result<PartsAndLocations<Self>, Error> {
430 let mut ret = PartsAndLocations::default();
431 let path = Path::new("index.html");
432 for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) {
433 let part = format!(
434 "<li><a href=\"{trailing_slash}index.html\">{crate_name}</a></li>",
435 trailing_slash = ensure_trailing_slash(crate_name),
436 );
437 ret.push(path.to_path_buf(), part);
438 }
439 Ok(ret)
440 }
441}
442
443#[derive(Serialize, Deserialize, Clone, Default, Debug)]
444struct Sources;
445type SourcesPart = Part<Sources, EscapedJson>;
446impl CciPart for SourcesPart {
447 type FileFormat = sorted_template::Js;
448 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
449 &crate_info.src_files_js
450 }
451}
452
453impl SourcesPart {
454 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
455 SortedTemplate::from_before_after(r"createSrcSidebar('[", r"]');")
459 }
460
461 fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result<PartsAndLocations<Self>, Error> {
462 let hierarchy = Rc::new(Hierarchy::default());
463 cx.shared
464 .local_sources
465 .iter()
466 .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
467 .for_each(|source| hierarchy.add_path(source));
468 let path = suffix_path("src-files.js", &cx.shared.resource_suffix);
469 let hierarchy = hierarchy.to_json_string();
470 let part = OrderedJson::array_unsorted([crate_name, &hierarchy]);
471 let part = EscapedJson::from(part);
472 Ok(PartsAndLocations::with(path, part))
473 }
474}
475
476#[derive(Debug, Default)]
478struct Hierarchy {
479 parent: Weak<Self>,
480 elem: OsString,
481 children: RefCell<FxIndexMap<OsString, Rc<Self>>>,
482 elems: RefCell<FxIndexSet<OsString>>,
483}
484
485impl Hierarchy {
486 fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
487 Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
488 }
489
490 fn to_json_string(&self) -> OrderedJson {
491 let subs = self.children.borrow();
492 let files = self.elems.borrow();
493 let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion"))
494 .unwrap();
495 let mut out = Vec::from([name]);
496 if !subs.is_empty() || !files.is_empty() {
497 let subs = subs.iter().map(|(_, s)| s.to_json_string());
498 out.push(OrderedJson::array_sorted(subs));
499 }
500 if !files.is_empty() {
501 let files = files
502 .iter()
503 .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap());
504 out.push(OrderedJson::array_sorted(files));
505 }
506 OrderedJson::array_unsorted(out)
507 }
508
509 fn add_path(self: &Rc<Self>, path: &Path) {
510 let mut h = Rc::clone(self);
511 let mut components = path
512 .components()
513 .filter(|component| matches!(component, Component::Normal(_) | Component::ParentDir))
514 .peekable();
515
516 assert!(components.peek().is_some(), "empty file path");
517 while let Some(component) = components.next() {
518 match component {
519 Component::Normal(s) => {
520 if components.peek().is_none() {
521 h.elems.borrow_mut().insert(s.to_owned());
522 break;
523 }
524 h = {
525 let mut children = h.children.borrow_mut();
526
527 if let Some(existing) = children.get(s) {
528 Rc::clone(existing)
529 } else {
530 let new_node = Rc::new(Self::with_parent(s.to_owned(), &h));
531 children.insert(s.to_owned(), Rc::clone(&new_node));
532 new_node
533 }
534 };
535 }
536 Component::ParentDir if let Some(parent) = h.parent.upgrade() => {
537 h = parent;
538 }
539 _ => {}
540 }
541 }
542 }
543}
544
545#[derive(Serialize, Deserialize, Clone, Default, Debug)]
546struct TypeAlias;
547type TypeAliasPart = Part<TypeAlias, OrderedJson>;
548impl CciPart for TypeAliasPart {
549 type FileFormat = sorted_template::Js;
550 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
551 &crate_info.type_impl
552 }
553}
554
555impl TypeAliasPart {
556 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
557 SortedTemplate::from_before_after(
558 r"(function() {
559 var type_impls = Object.fromEntries([",
560 r"]);
561 if (window.register_type_impls) {
562 window.register_type_impls(type_impls);
563 } else {
564 window.pending_type_impls = type_impls;
565 }
566})()",
567 )
568 }
569
570 fn get(
571 cx: &mut Context<'_>,
572 krate: &Crate,
573 crate_name_json: &OrderedJson,
574 ) -> Result<PartsAndLocations<Self>, Error> {
575 let mut path_parts = PartsAndLocations::default();
576
577 let mut type_impl_collector = TypeImplCollector {
578 aliased_types: IndexMap::default(),
579 visited_aliases: FxHashSet::default(),
580 cx,
581 };
582 DocVisitor::visit_crate(&mut type_impl_collector, krate);
583 let cx = type_impl_collector.cx;
584 let aliased_types = type_impl_collector.aliased_types;
585 for aliased_type in aliased_types.values() {
586 let impls = aliased_type.impl_.values().filter_map(
587 |AliasedTypeImpl { impl_, type_aliases }| {
588 let mut ret: Option<AliasSerializableImpl> = None;
589 for &(type_alias_fqp, type_alias_item) in type_aliases {
593 cx.id_map.borrow_mut().clear();
594 cx.deref_id_map.borrow_mut().clear();
595 let type_alias_fqp = join_path_syms(type_alias_fqp);
596 if let Some(ret) = &mut ret {
597 ret.aliases.push(type_alias_fqp);
598 } else {
599 let target_trait_did =
600 impl_.inner_impl().trait_.as_ref().map(|trait_| trait_.def_id());
601 let provided_methods;
602 let assoc_link = if let Some(target_trait_did) = target_trait_did {
603 provided_methods =
604 impl_.inner_impl().provided_trait_methods(cx.tcx());
605 AssocItemLink::GotoSource(
606 ItemId::DefId(target_trait_did),
607 &provided_methods,
608 )
609 } else {
610 AssocItemLink::Anchor(None)
611 };
612 let text = super::render_impl(
613 cx,
614 impl_,
615 type_alias_item,
616 assoc_link,
617 RenderMode::Normal,
618 None,
619 &[],
620 ImplRenderingParameters {
621 show_def_docs: true,
622 show_default_items: true,
623 show_non_assoc_items: true,
624 toggle_open_by_default: true,
625 },
626 )
627 .to_string();
628 let trait_ = impl_
630 .inner_impl()
631 .trait_
632 .as_ref()
633 .map(|trait_| format!("{:#}", print_path(trait_, cx)));
634 ret = Some(AliasSerializableImpl {
635 text,
636 trait_,
637 aliases: vec![type_alias_fqp],
638 })
639 }
640 }
641 ret
642 },
643 );
644
645 let mut path = PathBuf::from("type.impl");
646 for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
647 path.push(component.as_str());
648 }
649 let aliased_item_type = aliased_type.target_type;
650 path.push(format!(
651 "{aliased_item_type}.{}.js",
652 aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
653 ));
654
655 let part = OrderedJson::array_sorted(
656 impls.map(|impl_| OrderedJson::serialize(impl_).unwrap()),
657 );
658 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
659 }
660 Ok(path_parts)
661 }
662}
663
664#[derive(Serialize, Deserialize, Clone, Default, Debug)]
665struct TraitAlias;
666type TraitAliasPart = Part<TraitAlias, OrderedJson>;
667impl CciPart for TraitAliasPart {
668 type FileFormat = sorted_template::Js;
669 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
670 &crate_info.trait_impl
671 }
672}
673
674impl TraitAliasPart {
675 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
676 SortedTemplate::from_before_after(
677 r"(function() {
678 const implementors = Object.fromEntries([",
679 r"]);
680 if (window.register_implementors) {
681 window.register_implementors(implementors);
682 } else {
683 window.pending_implementors = implementors;
684 }
685})()",
686 )
687 }
688
689 fn get(
690 cx: &Context<'_>,
691 crate_name_json: &OrderedJson,
692 ) -> Result<PartsAndLocations<Self>, Error> {
693 let cache = &cx.shared.cache;
694 let mut path_parts = PartsAndLocations::default();
695 for (&did, imps) in &cache.implementors {
698 let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
706 Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
707 Some((_, t)) => (p, t),
708 None => continue,
709 },
710 None => match cache.external_paths.get(&did) {
711 Some((p, t)) => (p, t),
712 None => continue,
713 },
714 };
715
716 let mut implementors = imps
717 .iter()
718 .filter_map(|imp| {
719 if imp.impl_item.item_id.krate() == did.krate
727 || !imp.impl_item.item_id.is_local()
728 {
729 None
730 } else {
731 let impl_ = imp.inner_impl();
732 let print = print_impl(impl_, false, cx);
733 Some(Implementor {
734 text: format!("{}", print),
735 cmp_text: format!("{:#}", print),
736 synthetic: imp.inner_impl().kind.is_auto(),
737 types: collect_paths_for_type(&imp.inner_impl().for_, cache),
738 is_negative: impl_.is_negative_trait_impl(),
739 })
740 }
741 })
742 .peekable();
743
744 if implementors.peek().is_none() && !cache.paths.contains_key(&did) {
748 continue;
749 }
750
751 let mut path = PathBuf::from("trait.impl");
752 for component in &remote_path[..remote_path.len() - 1] {
753 path.push(component.as_str());
754 }
755 path.push(format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
756
757 let mut implementors = implementors.collect::<Vec<_>>();
758 implementors.sort_unstable_by(|a, b| compare_names(&a.cmp_text, &b.cmp_text));
761
762 let part = OrderedJson::array_unsorted(
763 implementors
764 .iter()
765 .map(OrderedJson::serialize)
766 .collect::<Result<Vec<_>, _>>()
767 .unwrap(),
768 );
769 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
770 }
771 Ok(path_parts)
772 }
773}
774
775struct Implementor {
776 text: String,
778 cmp_text: String,
781 synthetic: bool,
782 types: Vec<String>,
783 is_negative: bool,
784}
785
786impl Serialize for Implementor {
787 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
788 where
789 S: Serializer,
790 {
791 let mut seq = serializer.serialize_seq(None)?;
792 seq.serialize_element(&self.text)?;
793 seq.serialize_element(if self.is_negative { &1 } else { &0 })?;
794 if self.synthetic {
795 seq.serialize_element(&1)?;
796 seq.serialize_element(&self.types)?;
797 }
798 seq.end()
799 }
800}
801
802struct TypeImplCollector<'cx, 'cache, 'item> {
810 aliased_types: IndexMap<DefId, AliasedType<'cache, 'item>>,
812 visited_aliases: FxHashSet<DefId>,
813 cx: &'cache Context<'cx>,
814}
815
816struct AliasedType<'cache, 'item> {
832 target_fqp: &'cache [Symbol],
834 target_type: ItemType,
835 impl_: IndexMap<ItemId, AliasedTypeImpl<'cache, 'item>>,
838}
839
840struct AliasedTypeImpl<'cache, 'item> {
845 impl_: &'cache Impl,
846 type_aliases: Vec<(&'cache [Symbol], &'item Item)>,
847}
848
849impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> {
850 fn visit_item(&mut self, it: &'item Item) {
851 self.visit_item_recur(it);
852 let cache = &self.cx.shared.cache;
853 let ItemKind::TypeAliasItem(ref t) = it.kind else { return };
854 let Some(self_did) = it.item_id.as_def_id() else { return };
855 if !self.visited_aliases.insert(self_did) {
856 return;
857 }
858 let Some(target_did) = t.type_.def_id(cache) else { return };
859 let get_extern = { || cache.external_paths.get(&target_did) };
860 let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern)
861 else {
862 return;
863 };
864 let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
865 let impl_ = cache
866 .impls
867 .get(&target_did)
868 .into_iter()
869 .flatten()
870 .map(|impl_| {
871 (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() })
872 })
873 .collect();
874 AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
875 });
876 let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) };
877 let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
878 return;
879 };
880 let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
881 let mut seen_impls: FxHashSet<ItemId> =
885 cache.impls.get(&self_did).into_iter().flatten().map(|i| i.impl_item.item_id).collect();
886 for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
887 let Some(impl_did) = impl_item_id.as_def_id() else { continue };
895 let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
896 let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx());
897 if !reject_cx.types_may_unify(aliased_ty, for_ty) {
898 continue;
899 }
900 if !seen_impls.insert(*impl_item_id) {
902 continue;
903 }
904 aliased_type_impl.type_aliases.push((&self_fqp[..], it));
906 }
907 }
908}
909
910struct AliasSerializableImpl {
912 text: String,
913 trait_: Option<String>,
914 aliases: Vec<String>,
915}
916
917impl Serialize for AliasSerializableImpl {
918 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
919 where
920 S: Serializer,
921 {
922 let mut seq = serializer.serialize_seq(None)?;
923 seq.serialize_element(&self.text)?;
924 if let Some(trait_) = &self.trait_ {
925 seq.serialize_element(trait_)?;
926 } else {
927 seq.serialize_element(&0)?;
928 }
929 for type_ in &self.aliases {
930 seq.serialize_element(type_)?;
931 }
932 seq.end()
933 }
934}
935
936fn get_path_parts<T: CciPart>(
937 dst: &Path,
938 crates_info: &[CrateInfo],
939) -> FxIndexMap<PathBuf, Vec<String>> {
940 let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default();
941 crates_info.iter().flat_map(|crate_info| T::from_crate_info(crate_info).parts.iter()).for_each(
942 |(path, part)| {
943 let path = dst.join(path);
944 let part = part.to_string();
945 templates.entry(path).or_default().push(part);
946 },
947 );
948 templates
949}
950
951fn create_parents(path: &Path) -> Result<(), Error> {
953 let parent = path.parent().expect("should not have an empty path here");
954 try_err!(fs::create_dir_all(parent), parent);
955 Ok(())
956}
957
958fn read_template_or_blank<F, T: FileFormat>(
960 mut make_blank: F,
961 path: &Path,
962 should_merge: &ShouldMerge,
963) -> Result<SortedTemplate<T>, Error>
964where
965 F: FnMut() -> SortedTemplate<T>,
966{
967 if !should_merge.read_rendered_cci {
968 return Ok(make_blank());
969 }
970 match fs::read_to_string(path) {
971 Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
972 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
973 Err(e) => Err(Error::new(e, path)),
974 }
975}
976
977fn write_rendered_cci<T: CciPart, F>(
979 mut make_blank: F,
980 dst: &Path,
981 crates_info: &[CrateInfo],
982 should_merge: &ShouldMerge,
983) -> Result<(), Error>
984where
985 F: FnMut() -> SortedTemplate<T::FileFormat>,
986{
987 for (path, parts) in get_path_parts::<T>(dst, crates_info) {
989 create_parents(&path)?;
990 let mut template =
992 read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
993 for part in parts {
994 template.append(part);
995 }
996 let mut file = try_err!(File::create_buffered(&path), &path);
997 try_err!(write!(file, "{template}"), &path);
998 try_err!(file.flush(), &path);
999 }
1000 Ok(())
1001}
1002
1003#[cfg(test)]
1004mod tests;