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 regex::Regex;
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::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 =
70 build_index(krate, &mut cx.shared.cache, tcx, &cx.dst, &cx.shared.resource_suffix)?;
71
72 let crate_name = krate.name(cx.tcx());
73 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)?;
76 let info = CrateInfo {
77 version: CrateInfoVersion::V2,
78 src_files_js: SourcesPart::get(cx, &crate_name_json)?,
79 search_index,
80 all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
81 crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
82 trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
83 type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
84 };
85
86 if let Some(parts_out_dir) = &opt.parts_out_dir {
87 let mut parts_out_file = parts_out_dir.0.clone();
88 parts_out_file.push(&format!("{crate_name}.json"));
89 create_parents(&parts_out_file)?;
90 try_err!(
91 fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()),
92 &parts_out_dir.0
93 );
94 }
95
96 let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
97 crates.push(info);
98
99 if opt.should_merge.write_rendered_cci {
100 write_not_crate_specific(
101 &crates,
102 &cx.dst,
103 opt,
104 &cx.shared.style_files,
105 cx.shared.layout.css_file_extension.as_deref(),
106 &cx.shared.resource_suffix,
107 cx.info.include_sources,
108 )?;
109 match &opt.index_page {
110 Some(index_page) if opt.enable_index_page => {
111 let mut md_opts = opt.clone();
112 md_opts.output = cx.dst.clone();
113 md_opts.external_html = cx.shared.layout.external_html.clone();
114 try_err!(
115 crate::markdown::render_and_write(index_page, md_opts, cx.shared.edition()),
116 &index_page
117 );
118 }
119 None if opt.enable_index_page => {
120 write_rendered_cci::<CratesIndexPart, _>(
121 || CratesIndexPart::blank(cx),
122 &cx.dst,
123 &crates,
124 &opt.should_merge,
125 )?;
126 }
127 _ => {} }
129 }
130
131 cx.shared.fs.set_sync_only(false);
132 Ok(())
133}
134
135pub(crate) fn write_not_crate_specific(
139 crates: &[CrateInfo],
140 dst: &Path,
141 opt: &RenderOptions,
142 style_files: &[StylePath],
143 css_file_extension: Option<&Path>,
144 resource_suffix: &str,
145 include_sources: bool,
146) -> Result<(), Error> {
147 write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
148 write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
149 Ok(())
150}
151
152fn write_rendered_cross_crate_info(
153 crates: &[CrateInfo],
154 dst: &Path,
155 opt: &RenderOptions,
156 include_sources: bool,
157 resource_suffix: &str,
158) -> Result<(), Error> {
159 let m = &opt.should_merge;
160 if opt.should_emit_crate() {
161 if include_sources {
162 write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
163 }
164 crates
165 .iter()
166 .fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
167 .sort()
168 .write_to(dst, resource_suffix)?;
169 write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
170 }
171 write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
172 write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, crates, m)?;
173 Ok(())
174}
175
176fn write_static_files(
179 dst: &Path,
180 opt: &RenderOptions,
181 style_files: &[StylePath],
182 css_file_extension: Option<&Path>,
183 resource_suffix: &str,
184) -> Result<(), Error> {
185 let static_dir = dst.join("static.files");
186 try_err!(fs::create_dir_all(&static_dir), &static_dir);
187
188 for entry in style_files {
190 let theme = entry.basename()?;
191 let extension =
192 try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
193
194 if matches!(theme.as_str(), "light" | "dark" | "ayu") {
196 continue;
197 }
198
199 let bytes = try_err!(fs::read(&entry.path), &entry.path);
200 let filename = format!("{theme}{resource_suffix}.{extension}");
201 let dst_filename = dst.join(filename);
202 try_err!(fs::write(&dst_filename, bytes), &dst_filename);
203 }
204
205 if let Some(css) = css_file_extension {
208 let buffer = try_err!(fs::read_to_string(css), css);
209 let path = static_files::suffix_path("theme.css", resource_suffix);
210 let dst_path = dst.join(path);
211 try_err!(fs::write(&dst_path, buffer), &dst_path);
212 }
213
214 if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
215 static_files::for_each(|f: &static_files::StaticFile| {
216 let filename = static_dir.join(f.output_filename());
217 let contents: &[u8] =
218 if opt.disable_minification { f.src_bytes } else { f.minified_bytes };
219 fs::write(&filename, contents).map_err(|e| PathError::new(e, &filename))
220 })?;
221 }
222
223 Ok(())
224}
225
226#[derive(Serialize, Deserialize, Clone, Debug)]
228pub(crate) struct CrateInfo {
229 version: CrateInfoVersion,
230 src_files_js: PartsAndLocations<SourcesPart>,
231 search_index: SerializedSearchIndex,
232 all_crates: PartsAndLocations<AllCratesPart>,
233 crates_index: PartsAndLocations<CratesIndexPart>,
234 trait_impl: PartsAndLocations<TraitAliasPart>,
235 type_impl: PartsAndLocations<TypeAliasPart>,
236}
237
238impl CrateInfo {
239 pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
241 parts_paths
242 .iter()
243 .fold(Ok(Vec::new()), |acc, parts_path| {
244 let mut acc = acc?;
245 let dir = &parts_path.0;
246 acc.append(&mut try_err!(std::fs::read_dir(dir), dir.as_path())
247 .filter_map(|file| {
248 let to_crate_info = |file: Result<std::fs::DirEntry, std::io::Error>| -> Result<Option<CrateInfo>, Error> {
249 let file = try_err!(file, dir.as_path());
250 if file.path().extension() != Some(OsStr::new("json")) {
251 return Ok(None);
252 }
253 let parts = try_err!(fs::read(file.path()), file.path());
254 let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), file.path());
255 Ok(Some(parts))
256 };
257 to_crate_info(file).transpose()
258 })
259 .collect::<Result<Vec<CrateInfo>, Error>>()?);
260 Ok(acc)
261 })
262 }
263}
264
265#[derive(Serialize, Deserialize, Clone, Debug)]
273enum CrateInfoVersion {
274 V2,
275}
276
277#[derive(Serialize, Deserialize, Debug, Clone)]
279#[serde(transparent)]
280struct PartsAndLocations<P> {
281 parts: Vec<(PathBuf, P)>,
282}
283
284impl<P> Default for PartsAndLocations<P> {
285 fn default() -> Self {
286 Self { parts: Vec::default() }
287 }
288}
289
290impl<T, U> PartsAndLocations<Part<T, U>> {
291 fn push(&mut self, path: PathBuf, item: U) {
292 self.parts.push((path, Part { _artifact: PhantomData, item }));
293 }
294
295 fn with(path: PathBuf, part: U) -> Self {
297 let mut ret = Self::default();
298 ret.push(path, part);
299 ret
300 }
301}
302
303#[derive(Serialize, Deserialize, Debug, Clone)]
307#[serde(transparent)]
308struct Part<T, U> {
309 #[serde(skip)]
310 _artifact: PhantomData<T>,
311 item: U,
312}
313
314impl<T, U: fmt::Display> fmt::Display for Part<T, U> {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 write!(f, "{}", self.item)
318 }
319}
320
321trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
323 type FileFormat: sorted_template::FileFormat;
325 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
326}
327
328#[derive(Serialize, Deserialize, Clone, Default, Debug)]
329struct AllCrates;
330type AllCratesPart = Part<AllCrates, OrderedJson>;
331impl CciPart for AllCratesPart {
332 type FileFormat = sorted_template::Js;
333 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
334 &crate_info.all_crates
335 }
336}
337
338impl AllCratesPart {
339 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
340 SortedTemplate::from_before_after("window.ALL_CRATES = [", "];")
341 }
342
343 fn get(
344 crate_name_json: OrderedJson,
345 resource_suffix: &str,
346 ) -> Result<PartsAndLocations<Self>, Error> {
347 let path = suffix_path("crates.js", resource_suffix);
350 Ok(PartsAndLocations::with(path, crate_name_json))
351 }
352}
353
354fn hack_get_external_crate_names(
361 doc_root: &Path,
362 resource_suffix: &str,
363) -> Result<Vec<String>, Error> {
364 let path = doc_root.join(suffix_path("crates.js", resource_suffix));
365 let Ok(content) = fs::read_to_string(&path) else {
366 return Ok(Vec::default());
368 };
369 let regex = Regex::new(r"\[.*\]").unwrap();
372 let Some(content) = regex.find(&content) else {
373 return Err(Error::new("could not find crates list in crates.js", path));
374 };
375 let content: Vec<String> = try_err!(serde_json::from_str(content.as_str()), &path);
376 Ok(content)
377}
378
379#[derive(Serialize, Deserialize, Clone, Default, Debug)]
380struct CratesIndex;
381type CratesIndexPart = Part<CratesIndex, String>;
382impl CciPart for CratesIndexPart {
383 type FileFormat = sorted_template::Html;
384 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
385 &crate_info.crates_index
386 }
387}
388
389impl CratesIndexPart {
390 fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
391 let page = layout::Page {
392 title: "Index of crates",
393 short_title: "Crates",
394 css_class: "mod sys",
395 root_path: "./",
396 static_root_path: cx.shared.static_root_path.as_deref(),
397 description: "List of crates",
398 resource_suffix: &cx.shared.resource_suffix,
399 rust_logo: true,
400 };
401 let layout = &cx.shared.layout;
402 let style_files = &cx.shared.style_files;
403 const DELIMITER: &str = "\u{FFFC}"; let content = format!(
405 "<div class=\"main-heading\">\
406 <h1>List of all crates</h1>\
407 <rustdoc-toolbar></rustdoc-toolbar>\
408 </div>\
409 <ul class=\"all-items\">{DELIMITER}</ul>"
410 );
411 let template = layout::render(layout, &page, "", content, style_files);
412 SortedTemplate::from_template(&template, DELIMITER)
413 .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page")
414 }
415
416 fn get(crate_name: &str, external_crates: &[String]) -> Result<PartsAndLocations<Self>, Error> {
418 let mut ret = PartsAndLocations::default();
419 let path = Path::new("index.html");
420 for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) {
421 let part = format!(
422 "<li><a href=\"{trailing_slash}index.html\">{crate_name}</a></li>",
423 trailing_slash = ensure_trailing_slash(crate_name),
424 );
425 ret.push(path.to_path_buf(), part);
426 }
427 Ok(ret)
428 }
429}
430
431#[derive(Serialize, Deserialize, Clone, Default, Debug)]
432struct Sources;
433type SourcesPart = Part<Sources, EscapedJson>;
434impl CciPart for SourcesPart {
435 type FileFormat = sorted_template::Js;
436 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
437 &crate_info.src_files_js
438 }
439}
440
441impl SourcesPart {
442 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
443 SortedTemplate::from_before_after(r"createSrcSidebar('[", r"]');")
447 }
448
449 fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result<PartsAndLocations<Self>, Error> {
450 let hierarchy = Rc::new(Hierarchy::default());
451 cx.shared
452 .local_sources
453 .iter()
454 .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
455 .for_each(|source| hierarchy.add_path(source));
456 let path = suffix_path("src-files.js", &cx.shared.resource_suffix);
457 let hierarchy = hierarchy.to_json_string();
458 let part = OrderedJson::array_unsorted([crate_name, &hierarchy]);
459 let part = EscapedJson::from(part);
460 Ok(PartsAndLocations::with(path, part))
461 }
462}
463
464#[derive(Debug, Default)]
466struct Hierarchy {
467 parent: Weak<Self>,
468 elem: OsString,
469 children: RefCell<FxIndexMap<OsString, Rc<Self>>>,
470 elems: RefCell<FxIndexSet<OsString>>,
471}
472
473impl Hierarchy {
474 fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
475 Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
476 }
477
478 fn to_json_string(&self) -> OrderedJson {
479 let subs = self.children.borrow();
480 let files = self.elems.borrow();
481 let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion"))
482 .unwrap();
483 let mut out = Vec::from([name]);
484 if !subs.is_empty() || !files.is_empty() {
485 let subs = subs.iter().map(|(_, s)| s.to_json_string());
486 out.push(OrderedJson::array_sorted(subs));
487 }
488 if !files.is_empty() {
489 let files = files
490 .iter()
491 .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap());
492 out.push(OrderedJson::array_sorted(files));
493 }
494 OrderedJson::array_unsorted(out)
495 }
496
497 fn add_path(self: &Rc<Self>, path: &Path) {
498 let mut h = Rc::clone(self);
499 let mut elems = path
500 .components()
501 .filter_map(|s| match s {
502 Component::Normal(s) => Some(s.to_owned()),
503 Component::ParentDir => Some(OsString::from("..")),
504 _ => None,
505 })
506 .peekable();
507 loop {
508 let cur_elem = elems.next().expect("empty file path");
509 if cur_elem == ".." {
510 if let Some(parent) = h.parent.upgrade() {
511 h = parent;
512 }
513 continue;
514 }
515 if elems.peek().is_none() {
516 h.elems.borrow_mut().insert(cur_elem);
517 break;
518 } else {
519 let entry = Rc::clone(
520 h.children
521 .borrow_mut()
522 .entry(cur_elem.clone())
523 .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))),
524 );
525 h = entry;
526 }
527 }
528 }
529}
530
531#[derive(Serialize, Deserialize, Clone, Default, Debug)]
532struct TypeAlias;
533type TypeAliasPart = Part<TypeAlias, OrderedJson>;
534impl CciPart for TypeAliasPart {
535 type FileFormat = sorted_template::Js;
536 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
537 &crate_info.type_impl
538 }
539}
540
541impl TypeAliasPart {
542 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
543 SortedTemplate::from_before_after(
544 r"(function() {
545 var type_impls = Object.fromEntries([",
546 r"]);
547 if (window.register_type_impls) {
548 window.register_type_impls(type_impls);
549 } else {
550 window.pending_type_impls = type_impls;
551 }
552})()",
553 )
554 }
555
556 fn get(
557 cx: &mut Context<'_>,
558 krate: &Crate,
559 crate_name_json: &OrderedJson,
560 ) -> Result<PartsAndLocations<Self>, Error> {
561 let mut path_parts = PartsAndLocations::default();
562
563 let mut type_impl_collector = TypeImplCollector {
564 aliased_types: IndexMap::default(),
565 visited_aliases: FxHashSet::default(),
566 cx,
567 };
568 DocVisitor::visit_crate(&mut type_impl_collector, krate);
569 let cx = type_impl_collector.cx;
570 let aliased_types = type_impl_collector.aliased_types;
571 for aliased_type in aliased_types.values() {
572 let impls = aliased_type.impl_.values().filter_map(
573 |AliasedTypeImpl { impl_, type_aliases }| {
574 let mut ret: Option<AliasSerializableImpl> = None;
575 for &(type_alias_fqp, type_alias_item) in type_aliases {
579 cx.id_map.borrow_mut().clear();
580 cx.deref_id_map.borrow_mut().clear();
581 let type_alias_fqp = join_path_syms(type_alias_fqp);
582 if let Some(ret) = &mut ret {
583 ret.aliases.push(type_alias_fqp);
584 } else {
585 let target_trait_did =
586 impl_.inner_impl().trait_.as_ref().map(|trait_| trait_.def_id());
587 let provided_methods;
588 let assoc_link = if let Some(target_trait_did) = target_trait_did {
589 provided_methods =
590 impl_.inner_impl().provided_trait_methods(cx.tcx());
591 AssocItemLink::GotoSource(
592 ItemId::DefId(target_trait_did),
593 &provided_methods,
594 )
595 } else {
596 AssocItemLink::Anchor(None)
597 };
598 let text = super::render_impl(
599 cx,
600 impl_,
601 type_alias_item,
602 assoc_link,
603 RenderMode::Normal,
604 None,
605 &[],
606 ImplRenderingParameters {
607 show_def_docs: true,
608 show_default_items: true,
609 show_non_assoc_items: true,
610 toggle_open_by_default: true,
611 },
612 )
613 .to_string();
614 let trait_ = impl_
616 .inner_impl()
617 .trait_
618 .as_ref()
619 .map(|trait_| format!("{:#}", print_path(trait_, cx)));
620 ret = Some(AliasSerializableImpl {
621 text,
622 trait_,
623 aliases: vec![type_alias_fqp],
624 })
625 }
626 }
627 ret
628 },
629 );
630
631 let mut path = PathBuf::from("type.impl");
632 for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
633 path.push(component.as_str());
634 }
635 let aliased_item_type = aliased_type.target_type;
636 path.push(format!(
637 "{aliased_item_type}.{}.js",
638 aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
639 ));
640
641 let part = OrderedJson::array_sorted(
642 impls.map(|impl_| OrderedJson::serialize(impl_).unwrap()),
643 );
644 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
645 }
646 Ok(path_parts)
647 }
648}
649
650#[derive(Serialize, Deserialize, Clone, Default, Debug)]
651struct TraitAlias;
652type TraitAliasPart = Part<TraitAlias, OrderedJson>;
653impl CciPart for TraitAliasPart {
654 type FileFormat = sorted_template::Js;
655 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
656 &crate_info.trait_impl
657 }
658}
659
660impl TraitAliasPart {
661 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
662 SortedTemplate::from_before_after(
663 r"(function() {
664 var implementors = Object.fromEntries([",
665 r"]);
666 if (window.register_implementors) {
667 window.register_implementors(implementors);
668 } else {
669 window.pending_implementors = implementors;
670 }
671})()",
672 )
673 }
674
675 fn get(
676 cx: &Context<'_>,
677 crate_name_json: &OrderedJson,
678 ) -> Result<PartsAndLocations<Self>, Error> {
679 let cache = &cx.shared.cache;
680 let mut path_parts = PartsAndLocations::default();
681 for (&did, imps) in &cache.implementors {
684 let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
692 Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
693 Some((_, t)) => (p, t),
694 None => continue,
695 },
696 None => match cache.external_paths.get(&did) {
697 Some((p, t)) => (p, t),
698 None => continue,
699 },
700 };
701
702 let mut implementors = imps
703 .iter()
704 .filter_map(|imp| {
705 if imp.impl_item.item_id.krate() == did.krate
713 || !imp.impl_item.item_id.is_local()
714 {
715 None
716 } else {
717 Some(Implementor {
718 text: print_impl(imp.inner_impl(), false, cx).to_string(),
719 synthetic: imp.inner_impl().kind.is_auto(),
720 types: collect_paths_for_type(&imp.inner_impl().for_, cache),
721 })
722 }
723 })
724 .peekable();
725
726 if implementors.peek().is_none() && !cache.paths.contains_key(&did) {
730 continue;
731 }
732
733 let mut path = PathBuf::from("trait.impl");
734 for component in &remote_path[..remote_path.len() - 1] {
735 path.push(component.as_str());
736 }
737 path.push(format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
738
739 let part = OrderedJson::array_sorted(
740 implementors.map(|implementor| OrderedJson::serialize(implementor).unwrap()),
741 );
742 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
743 }
744 Ok(path_parts)
745 }
746}
747
748struct Implementor {
749 text: String,
750 synthetic: bool,
751 types: Vec<String>,
752}
753
754impl Serialize for Implementor {
755 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
756 where
757 S: Serializer,
758 {
759 let mut seq = serializer.serialize_seq(None)?;
760 seq.serialize_element(&self.text)?;
761 if self.synthetic {
762 seq.serialize_element(&1)?;
763 seq.serialize_element(&self.types)?;
764 }
765 seq.end()
766 }
767}
768
769struct TypeImplCollector<'cx, 'cache, 'item> {
777 aliased_types: IndexMap<DefId, AliasedType<'cache, 'item>>,
779 visited_aliases: FxHashSet<DefId>,
780 cx: &'cache Context<'cx>,
781}
782
783struct AliasedType<'cache, 'item> {
799 target_fqp: &'cache [Symbol],
801 target_type: ItemType,
802 impl_: IndexMap<ItemId, AliasedTypeImpl<'cache, 'item>>,
805}
806
807struct AliasedTypeImpl<'cache, 'item> {
812 impl_: &'cache Impl,
813 type_aliases: Vec<(&'cache [Symbol], &'item Item)>,
814}
815
816impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> {
817 fn visit_item(&mut self, it: &'item Item) {
818 self.visit_item_recur(it);
819 let cache = &self.cx.shared.cache;
820 let ItemKind::TypeAliasItem(ref t) = it.kind else { return };
821 let Some(self_did) = it.item_id.as_def_id() else { return };
822 if !self.visited_aliases.insert(self_did) {
823 return;
824 }
825 let Some(target_did) = t.type_.def_id(cache) else { return };
826 let get_extern = { || cache.external_paths.get(&target_did) };
827 let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern)
828 else {
829 return;
830 };
831 let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
832 let impl_ = cache
833 .impls
834 .get(&target_did)
835 .into_iter()
836 .flatten()
837 .map(|impl_| {
838 (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() })
839 })
840 .collect();
841 AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
842 });
843 let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) };
844 let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
845 return;
846 };
847 let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
848 let mut seen_impls: FxHashSet<ItemId> =
852 cache.impls.get(&self_did).into_iter().flatten().map(|i| i.impl_item.item_id).collect();
853 for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
854 let Some(impl_did) = impl_item_id.as_def_id() else { continue };
861 let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
862 let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx());
863 if !reject_cx.types_may_unify(aliased_ty, for_ty) {
864 continue;
865 }
866 if !seen_impls.insert(*impl_item_id) {
868 continue;
869 }
870 aliased_type_impl.type_aliases.push((&self_fqp[..], it));
872 }
873 }
874}
875
876struct AliasSerializableImpl {
878 text: String,
879 trait_: Option<String>,
880 aliases: Vec<String>,
881}
882
883impl Serialize for AliasSerializableImpl {
884 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
885 where
886 S: Serializer,
887 {
888 let mut seq = serializer.serialize_seq(None)?;
889 seq.serialize_element(&self.text)?;
890 if let Some(trait_) = &self.trait_ {
891 seq.serialize_element(trait_)?;
892 } else {
893 seq.serialize_element(&0)?;
894 }
895 for type_ in &self.aliases {
896 seq.serialize_element(type_)?;
897 }
898 seq.end()
899 }
900}
901
902fn get_path_parts<T: CciPart>(
903 dst: &Path,
904 crates_info: &[CrateInfo],
905) -> FxIndexMap<PathBuf, Vec<String>> {
906 let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default();
907 crates_info.iter().flat_map(|crate_info| T::from_crate_info(crate_info).parts.iter()).for_each(
908 |(path, part)| {
909 let path = dst.join(path);
910 let part = part.to_string();
911 templates.entry(path).or_default().push(part);
912 },
913 );
914 templates
915}
916
917fn create_parents(path: &Path) -> Result<(), Error> {
919 let parent = path.parent().expect("should not have an empty path here");
920 try_err!(fs::create_dir_all(parent), parent);
921 Ok(())
922}
923
924fn read_template_or_blank<F, T: FileFormat>(
926 mut make_blank: F,
927 path: &Path,
928 should_merge: &ShouldMerge,
929) -> Result<SortedTemplate<T>, Error>
930where
931 F: FnMut() -> SortedTemplate<T>,
932{
933 if !should_merge.read_rendered_cci {
934 return Ok(make_blank());
935 }
936 match fs::read_to_string(path) {
937 Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
938 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
939 Err(e) => Err(Error::new(e, path)),
940 }
941}
942
943fn write_rendered_cci<T: CciPart, F>(
945 mut make_blank: F,
946 dst: &Path,
947 crates_info: &[CrateInfo],
948 should_merge: &ShouldMerge,
949) -> Result<(), Error>
950where
951 F: FnMut() -> SortedTemplate<T::FileFormat>,
952{
953 for (path, parts) in get_path_parts::<T>(dst, crates_info) {
955 create_parents(&path)?;
956 let mut template =
958 read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
959 for part in parts {
960 template.append(part);
961 }
962 let mut file = try_err!(File::create_buffered(&path), &path);
963 try_err!(write!(file, "{template}"), &path);
964 try_err!(file.flush(), &path);
965 }
966 Ok(())
967}
968
969#[cfg(test)]
970mod tests;