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