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 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::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 search_index =
69 build_index(krate, &mut cx.shared.cache, tcx, &cx.dst, &cx.shared.resource_suffix)?;
70
71 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::V2,
77 src_files_js: SourcesPart::get(cx, &crate_name_json)?,
78 search_index,
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, resource_suffix)?;
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 resource_suffix: &str,
155) -> Result<(), Error> {
156 let m = &opt.should_merge;
157 if opt.should_emit_crate() {
158 if include_sources {
159 write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
160 }
161 crates
162 .iter()
163 .fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
164 .sort()
165 .write_to(dst, resource_suffix)?;
166 write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
167 }
168 write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
169 write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, crates, m)?;
170 Ok(())
171}
172
173fn write_static_files(
176 dst: &Path,
177 opt: &RenderOptions,
178 style_files: &[StylePath],
179 css_file_extension: Option<&Path>,
180 resource_suffix: &str,
181) -> Result<(), Error> {
182 let static_dir = dst.join("static.files");
183 try_err!(fs::create_dir_all(&static_dir), &static_dir);
184
185 for entry in style_files {
187 let theme = entry.basename()?;
188 let extension =
189 try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
190
191 if matches!(theme.as_str(), "light" | "dark" | "ayu") {
193 continue;
194 }
195
196 let bytes = try_err!(fs::read(&entry.path), &entry.path);
197 let filename = format!("{theme}{resource_suffix}.{extension}");
198 let dst_filename = dst.join(filename);
199 try_err!(fs::write(&dst_filename, bytes), &dst_filename);
200 }
201
202 if let Some(css) = css_file_extension {
205 let buffer = try_err!(fs::read_to_string(css), css);
206 let path = static_files::suffix_path("theme.css", resource_suffix);
207 let dst_path = dst.join(path);
208 try_err!(fs::write(&dst_path, buffer), &dst_path);
209 }
210
211 if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
212 static_files::for_each(|f: &static_files::StaticFile| {
213 let filename = static_dir.join(f.output_filename());
214 let contents: &[u8] =
215 if opt.disable_minification { f.src_bytes } else { f.minified_bytes };
216 fs::write(&filename, contents).map_err(|e| PathError::new(e, &filename))
217 })?;
218 }
219
220 Ok(())
221}
222
223#[derive(Serialize, Deserialize, Clone, Debug)]
225pub(crate) struct CrateInfo {
226 version: CrateInfoVersion,
227 src_files_js: PartsAndLocations<SourcesPart>,
228 search_index: SerializedSearchIndex,
229 all_crates: PartsAndLocations<AllCratesPart>,
230 crates_index: PartsAndLocations<CratesIndexPart>,
231 trait_impl: PartsAndLocations<TraitAliasPart>,
232 type_impl: PartsAndLocations<TypeAliasPart>,
233}
234
235impl CrateInfo {
236 pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
238 parts_paths
239 .iter()
240 .map(|parts_path| {
241 let path = &parts_path.0;
242 let parts = try_err!(fs::read(path), &path);
243 let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
244 Ok::<_, Error>(parts)
245 })
246 .collect::<Result<Vec<CrateInfo>, Error>>()
247 }
248}
249
250#[derive(Serialize, Deserialize, Clone, Debug)]
258enum CrateInfoVersion {
259 V2,
260}
261
262#[derive(Serialize, Deserialize, Debug, Clone)]
264#[serde(transparent)]
265struct PartsAndLocations<P> {
266 parts: Vec<(PathBuf, P)>,
267}
268
269impl<P> Default for PartsAndLocations<P> {
270 fn default() -> Self {
271 Self { parts: Vec::default() }
272 }
273}
274
275impl<T, U> PartsAndLocations<Part<T, U>> {
276 fn push(&mut self, path: PathBuf, item: U) {
277 self.parts.push((path, Part { _artifact: PhantomData, item }));
278 }
279
280 fn with(path: PathBuf, part: U) -> Self {
282 let mut ret = Self::default();
283 ret.push(path, part);
284 ret
285 }
286}
287
288#[derive(Serialize, Deserialize, Debug, Clone)]
292#[serde(transparent)]
293struct Part<T, U> {
294 #[serde(skip)]
295 _artifact: PhantomData<T>,
296 item: U,
297}
298
299impl<T, U: fmt::Display> fmt::Display for Part<T, U> {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 write!(f, "{}", self.item)
303 }
304}
305
306trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
308 type FileFormat: sorted_template::FileFormat;
310 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
311}
312
313#[derive(Serialize, Deserialize, Clone, Default, Debug)]
314struct AllCrates;
315type AllCratesPart = Part<AllCrates, OrderedJson>;
316impl CciPart for AllCratesPart {
317 type FileFormat = sorted_template::Js;
318 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
319 &crate_info.all_crates
320 }
321}
322
323impl AllCratesPart {
324 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
325 SortedTemplate::from_before_after("window.ALL_CRATES = [", "];")
326 }
327
328 fn get(
329 crate_name_json: OrderedJson,
330 resource_suffix: &str,
331 ) -> Result<PartsAndLocations<Self>, Error> {
332 let path = suffix_path("crates.js", resource_suffix);
335 Ok(PartsAndLocations::with(path, crate_name_json))
336 }
337}
338
339fn hack_get_external_crate_names(
346 doc_root: &Path,
347 resource_suffix: &str,
348) -> Result<Vec<String>, Error> {
349 let path = doc_root.join(suffix_path("crates.js", resource_suffix));
350 let Ok(content) = fs::read_to_string(&path) else {
351 return Ok(Vec::default());
353 };
354 let regex = Regex::new(r"\[.*\]").unwrap();
357 let Some(content) = regex.find(&content) else {
358 return Err(Error::new("could not find crates list in crates.js", path));
359 };
360 let content: Vec<String> = try_err!(serde_json::from_str(content.as_str()), &path);
361 Ok(content)
362}
363
364#[derive(Serialize, Deserialize, Clone, Default, Debug)]
365struct CratesIndex;
366type CratesIndexPart = Part<CratesIndex, String>;
367impl CciPart for CratesIndexPart {
368 type FileFormat = sorted_template::Html;
369 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
370 &crate_info.crates_index
371 }
372}
373
374impl CratesIndexPart {
375 fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
376 let page = layout::Page {
377 title: "Index of crates",
378 short_title: "Crates",
379 css_class: "mod sys",
380 root_path: "./",
381 static_root_path: cx.shared.static_root_path.as_deref(),
382 description: "List of crates",
383 resource_suffix: &cx.shared.resource_suffix,
384 rust_logo: true,
385 };
386 let layout = &cx.shared.layout;
387 let style_files = &cx.shared.style_files;
388 const DELIMITER: &str = "\u{FFFC}"; let content = format!(
390 "<div class=\"main-heading\">\
391 <h1>List of all crates</h1>\
392 <rustdoc-toolbar></rustdoc-toolbar>\
393 </div>\
394 <ul class=\"all-items\">{DELIMITER}</ul>"
395 );
396 let template = layout::render(layout, &page, "", content, style_files);
397 SortedTemplate::from_template(&template, DELIMITER)
398 .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page")
399 }
400
401 fn get(crate_name: &str, external_crates: &[String]) -> Result<PartsAndLocations<Self>, Error> {
403 let mut ret = PartsAndLocations::default();
404 let path = Path::new("index.html");
405 for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) {
406 let part = format!(
407 "<li><a href=\"{trailing_slash}index.html\">{crate_name}</a></li>",
408 trailing_slash = ensure_trailing_slash(crate_name),
409 );
410 ret.push(path.to_path_buf(), part);
411 }
412 Ok(ret)
413 }
414}
415
416#[derive(Serialize, Deserialize, Clone, Default, Debug)]
417struct Sources;
418type SourcesPart = Part<Sources, EscapedJson>;
419impl CciPart for SourcesPart {
420 type FileFormat = sorted_template::Js;
421 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
422 &crate_info.src_files_js
423 }
424}
425
426impl SourcesPart {
427 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
428 SortedTemplate::from_before_after(r"createSrcSidebar('[", r"]');")
432 }
433
434 fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result<PartsAndLocations<Self>, Error> {
435 let hierarchy = Rc::new(Hierarchy::default());
436 cx.shared
437 .local_sources
438 .iter()
439 .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
440 .for_each(|source| hierarchy.add_path(source));
441 let path = suffix_path("src-files.js", &cx.shared.resource_suffix);
442 let hierarchy = hierarchy.to_json_string();
443 let part = OrderedJson::array_unsorted([crate_name, &hierarchy]);
444 let part = EscapedJson::from(part);
445 Ok(PartsAndLocations::with(path, part))
446 }
447}
448
449#[derive(Debug, Default)]
451struct Hierarchy {
452 parent: Weak<Self>,
453 elem: OsString,
454 children: RefCell<FxIndexMap<OsString, Rc<Self>>>,
455 elems: RefCell<FxIndexSet<OsString>>,
456}
457
458impl Hierarchy {
459 fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
460 Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
461 }
462
463 fn to_json_string(&self) -> OrderedJson {
464 let subs = self.children.borrow();
465 let files = self.elems.borrow();
466 let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion"))
467 .unwrap();
468 let mut out = Vec::from([name]);
469 if !subs.is_empty() || !files.is_empty() {
470 let subs = subs.iter().map(|(_, s)| s.to_json_string());
471 out.push(OrderedJson::array_sorted(subs));
472 }
473 if !files.is_empty() {
474 let files = files
475 .iter()
476 .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap());
477 out.push(OrderedJson::array_sorted(files));
478 }
479 OrderedJson::array_unsorted(out)
480 }
481
482 fn add_path(self: &Rc<Self>, path: &Path) {
483 let mut h = Rc::clone(self);
484 let mut elems = path
485 .components()
486 .filter_map(|s| match s {
487 Component::Normal(s) => Some(s.to_owned()),
488 Component::ParentDir => Some(OsString::from("..")),
489 _ => None,
490 })
491 .peekable();
492 loop {
493 let cur_elem = elems.next().expect("empty file path");
494 if cur_elem == ".." {
495 if let Some(parent) = h.parent.upgrade() {
496 h = parent;
497 }
498 continue;
499 }
500 if elems.peek().is_none() {
501 h.elems.borrow_mut().insert(cur_elem);
502 break;
503 } else {
504 let entry = Rc::clone(
505 h.children
506 .borrow_mut()
507 .entry(cur_elem.clone())
508 .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))),
509 );
510 h = entry;
511 }
512 }
513 }
514}
515
516#[derive(Serialize, Deserialize, Clone, Default, Debug)]
517struct TypeAlias;
518type TypeAliasPart = Part<TypeAlias, OrderedJson>;
519impl CciPart for TypeAliasPart {
520 type FileFormat = sorted_template::Js;
521 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
522 &crate_info.type_impl
523 }
524}
525
526impl TypeAliasPart {
527 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
528 SortedTemplate::from_before_after(
529 r"(function() {
530 var type_impls = Object.fromEntries([",
531 r"]);
532 if (window.register_type_impls) {
533 window.register_type_impls(type_impls);
534 } else {
535 window.pending_type_impls = type_impls;
536 }
537})()",
538 )
539 }
540
541 fn get(
542 cx: &mut Context<'_>,
543 krate: &Crate,
544 crate_name_json: &OrderedJson,
545 ) -> Result<PartsAndLocations<Self>, Error> {
546 let mut path_parts = PartsAndLocations::default();
547
548 let mut type_impl_collector = TypeImplCollector {
549 aliased_types: IndexMap::default(),
550 visited_aliases: FxHashSet::default(),
551 cx,
552 };
553 DocVisitor::visit_crate(&mut type_impl_collector, krate);
554 let cx = type_impl_collector.cx;
555 let aliased_types = type_impl_collector.aliased_types;
556 for aliased_type in aliased_types.values() {
557 let impls = aliased_type.impl_.values().filter_map(
558 |AliasedTypeImpl { impl_, type_aliases }| {
559 let mut ret: Option<AliasSerializableImpl> = None;
560 for &(type_alias_fqp, type_alias_item) in type_aliases {
564 cx.id_map.borrow_mut().clear();
565 cx.deref_id_map.borrow_mut().clear();
566 let type_alias_fqp = join_path_syms(type_alias_fqp);
567 if let Some(ret) = &mut ret {
568 ret.aliases.push(type_alias_fqp);
569 } else {
570 let target_did = impl_
571 .inner_impl()
572 .trait_
573 .as_ref()
574 .map(|trait_| trait_.def_id())
575 .or_else(|| impl_.inner_impl().for_.def_id(&cx.shared.cache));
576 let provided_methods;
577 let assoc_link = if let Some(target_did) = target_did {
578 provided_methods =
579 impl_.inner_impl().provided_trait_methods(cx.tcx());
580 AssocItemLink::GotoSource(
581 ItemId::DefId(target_did),
582 &provided_methods,
583 )
584 } else {
585 AssocItemLink::Anchor(None)
586 };
587 let text = super::render_impl(
588 cx,
589 impl_,
590 type_alias_item,
591 assoc_link,
592 RenderMode::Normal,
593 None,
594 &[],
595 ImplRenderingParameters {
596 show_def_docs: true,
597 show_default_items: true,
598 show_non_assoc_items: true,
599 toggle_open_by_default: true,
600 },
601 )
602 .to_string();
603 let trait_ = impl_
605 .inner_impl()
606 .trait_
607 .as_ref()
608 .map(|trait_| format!("{:#}", trait_.print(cx)));
609 ret = Some(AliasSerializableImpl {
610 text,
611 trait_,
612 aliases: vec![type_alias_fqp],
613 })
614 }
615 }
616 ret
617 },
618 );
619
620 let mut path = PathBuf::from("type.impl");
621 for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
622 path.push(component.as_str());
623 }
624 let aliased_item_type = aliased_type.target_type;
625 path.push(format!(
626 "{aliased_item_type}.{}.js",
627 aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
628 ));
629
630 let part = OrderedJson::array_sorted(
631 impls.map(|impl_| OrderedJson::serialize(impl_).unwrap()),
632 );
633 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
634 }
635 Ok(path_parts)
636 }
637}
638
639#[derive(Serialize, Deserialize, Clone, Default, Debug)]
640struct TraitAlias;
641type TraitAliasPart = Part<TraitAlias, OrderedJson>;
642impl CciPart for TraitAliasPart {
643 type FileFormat = sorted_template::Js;
644 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
645 &crate_info.trait_impl
646 }
647}
648
649impl TraitAliasPart {
650 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
651 SortedTemplate::from_before_after(
652 r"(function() {
653 var implementors = Object.fromEntries([",
654 r"]);
655 if (window.register_implementors) {
656 window.register_implementors(implementors);
657 } else {
658 window.pending_implementors = implementors;
659 }
660})()",
661 )
662 }
663
664 fn get(
665 cx: &Context<'_>,
666 crate_name_json: &OrderedJson,
667 ) -> Result<PartsAndLocations<Self>, Error> {
668 let cache = &cx.shared.cache;
669 let mut path_parts = PartsAndLocations::default();
670 for (&did, imps) in &cache.implementors {
673 let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
681 Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
682 Some((_, t)) => (p, t),
683 None => continue,
684 },
685 None => match cache.external_paths.get(&did) {
686 Some((p, t)) => (p, t),
687 None => continue,
688 },
689 };
690
691 let mut implementors = imps
692 .iter()
693 .filter_map(|imp| {
694 if imp.impl_item.item_id.krate() == did.krate
702 || !imp.impl_item.item_id.is_local()
703 {
704 None
705 } else {
706 Some(Implementor {
707 text: imp.inner_impl().print(false, cx).to_string(),
708 synthetic: imp.inner_impl().kind.is_auto(),
709 types: collect_paths_for_type(&imp.inner_impl().for_, cache),
710 })
711 }
712 })
713 .peekable();
714
715 if implementors.peek().is_none() && !cache.paths.contains_key(&did) {
719 continue;
720 }
721
722 let mut path = PathBuf::from("trait.impl");
723 for component in &remote_path[..remote_path.len() - 1] {
724 path.push(component.as_str());
725 }
726 path.push(format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
727
728 let part = OrderedJson::array_sorted(
729 implementors.map(|implementor| OrderedJson::serialize(implementor).unwrap()),
730 );
731 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
732 }
733 Ok(path_parts)
734 }
735}
736
737struct Implementor {
738 text: String,
739 synthetic: bool,
740 types: Vec<String>,
741}
742
743impl Serialize for Implementor {
744 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
745 where
746 S: Serializer,
747 {
748 let mut seq = serializer.serialize_seq(None)?;
749 seq.serialize_element(&self.text)?;
750 if self.synthetic {
751 seq.serialize_element(&1)?;
752 seq.serialize_element(&self.types)?;
753 }
754 seq.end()
755 }
756}
757
758struct TypeImplCollector<'cx, 'cache, 'item> {
766 aliased_types: IndexMap<DefId, AliasedType<'cache, 'item>>,
768 visited_aliases: FxHashSet<DefId>,
769 cx: &'cache Context<'cx>,
770}
771
772struct AliasedType<'cache, 'item> {
788 target_fqp: &'cache [Symbol],
790 target_type: ItemType,
791 impl_: IndexMap<ItemId, AliasedTypeImpl<'cache, 'item>>,
794}
795
796struct AliasedTypeImpl<'cache, 'item> {
801 impl_: &'cache Impl,
802 type_aliases: Vec<(&'cache [Symbol], &'item Item)>,
803}
804
805impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> {
806 fn visit_item(&mut self, it: &'item Item) {
807 self.visit_item_recur(it);
808 let cache = &self.cx.shared.cache;
809 let ItemKind::TypeAliasItem(ref t) = it.kind else { return };
810 let Some(self_did) = it.item_id.as_def_id() else { return };
811 if !self.visited_aliases.insert(self_did) {
812 return;
813 }
814 let Some(target_did) = t.type_.def_id(cache) else { return };
815 let get_extern = { || cache.external_paths.get(&target_did) };
816 let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern)
817 else {
818 return;
819 };
820 let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
821 let impl_ = cache
822 .impls
823 .get(&target_did)
824 .into_iter()
825 .flatten()
826 .map(|impl_| {
827 (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() })
828 })
829 .collect();
830 AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
831 });
832 let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) };
833 let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
834 return;
835 };
836 let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
837 let mut seen_impls: FxHashSet<ItemId> =
841 cache.impls.get(&self_did).into_iter().flatten().map(|i| i.impl_item.item_id).collect();
842 for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
843 let Some(impl_did) = impl_item_id.as_def_id() else { continue };
850 let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
851 let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx());
852 if !reject_cx.types_may_unify(aliased_ty, for_ty) {
853 continue;
854 }
855 if !seen_impls.insert(*impl_item_id) {
857 continue;
858 }
859 aliased_type_impl.type_aliases.push((&self_fqp[..], it));
861 }
862 }
863}
864
865struct AliasSerializableImpl {
867 text: String,
868 trait_: Option<String>,
869 aliases: Vec<String>,
870}
871
872impl Serialize for AliasSerializableImpl {
873 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
874 where
875 S: Serializer,
876 {
877 let mut seq = serializer.serialize_seq(None)?;
878 seq.serialize_element(&self.text)?;
879 if let Some(trait_) = &self.trait_ {
880 seq.serialize_element(trait_)?;
881 } else {
882 seq.serialize_element(&0)?;
883 }
884 for type_ in &self.aliases {
885 seq.serialize_element(type_)?;
886 }
887 seq.end()
888 }
889}
890
891fn get_path_parts<T: CciPart>(
892 dst: &Path,
893 crates_info: &[CrateInfo],
894) -> FxIndexMap<PathBuf, Vec<String>> {
895 let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default();
896 crates_info.iter().flat_map(|crate_info| T::from_crate_info(crate_info).parts.iter()).for_each(
897 |(path, part)| {
898 let path = dst.join(path);
899 let part = part.to_string();
900 templates.entry(path).or_default().push(part);
901 },
902 );
903 templates
904}
905
906fn create_parents(path: &Path) -> Result<(), Error> {
908 let parent = path.parent().expect("should not have an empty path here");
909 try_err!(fs::create_dir_all(parent), parent);
910 Ok(())
911}
912
913fn read_template_or_blank<F, T: FileFormat>(
915 mut make_blank: F,
916 path: &Path,
917 should_merge: &ShouldMerge,
918) -> Result<SortedTemplate<T>, Error>
919where
920 F: FnMut() -> SortedTemplate<T>,
921{
922 if !should_merge.read_rendered_cci {
923 return Ok(make_blank());
924 }
925 match fs::read_to_string(path) {
926 Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
927 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
928 Err(e) => Err(Error::new(e, path)),
929 }
930}
931
932fn write_rendered_cci<T: CciPart, F>(
934 mut make_blank: F,
935 dst: &Path,
936 crates_info: &[CrateInfo],
937 should_merge: &ShouldMerge,
938) -> Result<(), Error>
939where
940 F: FnMut() -> SortedTemplate<T::FileFormat>,
941{
942 for (path, parts) in get_path_parts::<T>(dst, crates_info) {
944 create_parents(&path)?;
945 let mut template =
947 read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
948 for part in parts {
949 template.append(part);
950 }
951 let mut file = try_err!(File::create_buffered(&path), &path);
952 try_err!(write!(file, "{template}"), &path);
953 try_err!(file.flush(), &path);
954 }
955 Ok(())
956}
957
958#[cfg(test)]
959mod tests;