1use std::borrow::Cow;
17use std::cell::RefCell;
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::fx::{FxHashSet, FxIndexMap, FxIndexSet};
31use rustc_middle::ty::TyCtxt;
32use rustc_middle::ty::fast_reject::DeepRejectCtxt;
33use rustc_session::Session;
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::{
54 AssocItemLink, ImplRenderingParameters, StylePath, scrape_examples_help,
55};
56use crate::html::static_files::{self, suffix_path};
57use crate::visit::DocVisitor;
58use crate::{DOC_RUST_LANG_ORG_VERSION, try_err, try_none};
59
60mod flock;
61
62pub(crate) fn write_shared(
63 cx: &mut Context<'_>,
64 krate: &Crate,
65 opt: &RenderOptions,
66 tcx: TyCtxt<'_>,
67) -> Result<(), Error> {
68 cx.shared.fs.set_sync_only(true);
70 let lock_file = cx.dst.join(".lock");
71 let _lock = try_err!(flock::Lock::new(&lock_file), &lock_file);
73
74 let search_index = build_index(
75 krate,
76 &mut cx.shared.cache,
77 tcx,
78 &cx.dst,
79 &cx.shared.resource_suffix,
80 &opt.should_merge,
81 )?;
82
83 let crate_name = krate.name(cx.tcx());
84 let crate_name = crate_name.as_str(); let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); let all_crates_entry_json = OrderedJson::serialize(AllCratesEntry {
87 heading: Cow::Borrowed(&opt.crate_list_heading),
88 crate_name: Cow::Borrowed(crate_name),
89 })
90 .unwrap(); let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
93 let info = CrateInfo {
94 version: CrateInfoVersion::V2,
95 src_files_js: SourcesPart::get(cx, &crate_name_json)?,
96 search_index,
97 all_crates: AllCratesPart::get(all_crates_entry_json, &cx.shared.resource_suffix)?,
98 crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
99 trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
100 type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
101 };
102
103 if let Some(parts_out_dir) = &opt.parts_out_dir {
104 let mut parts_out_file = parts_out_dir.0.clone();
105 parts_out_file.push(&format!("{crate_name}.json"));
106 create_parents(&parts_out_file)?;
107 try_err!(
108 fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()),
109 &parts_out_dir.0
110 );
111 }
112
113 let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
114 crates.push(info);
115
116 if opt.should_merge.write_rendered_cci {
117 write_not_crate_specific(
118 &crates,
119 &cx.dst,
120 opt,
121 &cx.shared.style_files,
122 cx.shared.layout.css_file_extension.as_deref(),
123 &cx.shared.resource_suffix,
124 cx.info.include_sources,
125 &cx.shared.layout,
126 cx.sess(),
127 )?;
128 }
129
130 cx.shared.fs.set_sync_only(false);
131 Ok(())
132}
133
134pub(crate) fn write_not_crate_specific(
138 crates: &[CrateInfo],
139 dst: &Path,
140 opt: &RenderOptions,
141 style_files: &[StylePath],
142 css_file_extension: Option<&Path>,
143 resource_suffix: &str,
144 include_sources: bool,
145 layout: &layout::Layout,
146 sess: &Session,
147) -> Result<(), Error> {
148 write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
149 write_resources(dst, opt, style_files, css_file_extension, resource_suffix)?;
150 match &opt.index_page {
152 Some(index_page) if opt.enable_index_page => {
153 let mut md_opts = opt.clone();
154 md_opts.output = dst.to_path_buf();
155 md_opts.external_html = layout.external_html.clone();
156 let file = try_err!(sess.source_map().load_file(&index_page), &index_page);
157 try_err!(crate::markdown::render_and_write(file, md_opts, sess.edition()), &index_page);
158 }
159 None if opt.enable_index_page => {
160 write_rendered_cci::<CratesIndexPart, _>(
161 || CratesIndexPart::blank(layout, opt, style_files),
162 &dst,
163 &crates,
164 &opt.should_merge,
165 )?;
166 }
167 _ => {} }
169
170 if opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
171 let settings_file = dst.join("settings.html");
178 let help_file = dst.join("help.html");
179 let scrape_examples_help_file = dst.join("scrape-examples-help.html");
180
181 let page = layout::Page {
182 title: "Settings",
183 short_title: "Settings",
184 css_class: "mod sys",
185 root_path: "./",
186 static_root_path: opt.static_root_path.as_deref(),
187 description: "Settings of Rustdoc",
188 resource_suffix: &opt.resource_suffix,
189 rust_logo: true,
190 };
191 let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
192 let v = layout::render(
193 &layout,
194 &page,
195 sidebar,
196 fmt::from_fn(|buf| {
197 write!(
198 buf,
199 "<div class=\"main-heading\">\
200 <h1>Rustdoc settings</h1>\
201 <span class=\"out-of-band\">\
202 <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
203 Back\
204 </a>\
205 </span>\
206 </div>\
207 <noscript>\
208 <section>\
209 You need to enable JavaScript be able to update your settings.\
210 </section>\
211 </noscript>\
212 <script defer src=\"{static_root_path}{settings_js}\"></script>",
213 static_root_path = page.get_static_root_path(),
214 settings_js = static_files::STATIC_FILES.settings_js,
215 )?;
216 for file in style_files {
221 if let Ok(theme) = file.basename() {
222 write!(
223 buf,
224 "<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
225 as=\"style\">",
226 root_path = page.static_root_path.unwrap_or(""),
227 suffix = page.resource_suffix,
228 )?;
229 }
230 }
231 Ok(())
232 }),
233 &style_files,
234 );
235 try_err!(std::fs::write(&settings_file, v), &settings_file);
236
237 let page = layout::Page {
238 title: "Help",
239 short_title: "Help",
240 css_class: "mod sys",
241 root_path: "./",
242 static_root_path: opt.static_root_path.as_deref(),
243 description: "Documentation for Rustdoc",
244 resource_suffix: &opt.resource_suffix,
245 rust_logo: true,
246 };
247 let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
248 let v = layout::render(
249 &layout,
250 &page,
251 sidebar,
252 format_args!(
253 "<div class=\"main-heading\">\
254 <h1>Rustdoc help</h1>\
255 <span class=\"out-of-band\">\
256 <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
257 Back\
258 </a>\
259 </span>\
260 </div>\
261 <noscript>\
262 <section>\
263 <p>You need to enable JavaScript to use keyboard commands or search.</p>\
264 <p>For more information, browse the <a href=\"{DOC_RUST_LANG_ORG_VERSION}/rustdoc/\">rustdoc handbook</a>.</p>\
265 </section>\
266 </noscript>",
267 ),
268 &style_files,
269 );
270 try_err!(std::fs::write(&help_file, v), &help_file);
271
272 if layout.scrape_examples_extension {
273 let page = layout::Page {
274 title: "About scraped examples",
275 short_title: "About scraped examples",
276 css_class: "mod sys",
277 root_path: "./",
278 static_root_path: opt.static_root_path.as_deref(),
279 description: "How the scraped examples feature works in Rustdoc",
280 resource_suffix: &opt.resource_suffix,
281 rust_logo: true,
282 };
283 let v = layout::render(&layout, &page, "", scrape_examples_help(), &style_files);
284 try_err!(std::fs::write(&scrape_examples_help_file, v), &scrape_examples_help_file);
285 }
286 }
287 Ok(())
288}
289
290fn write_rendered_cross_crate_info(
291 crates: &[CrateInfo],
292 dst: &Path,
293 opt: &RenderOptions,
294 include_sources: bool,
295 resource_suffix: &str,
296) -> Result<(), Error> {
297 let m = &opt.should_merge;
298 if opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
299 if include_sources {
300 write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
301 }
302 crates
303 .iter()
304 .fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
305 .sort()
306 .write_to(dst, resource_suffix)?;
307 write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
308 }
309 write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
310 write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, crates, m)?;
311 Ok(())
312}
313
314fn write_resources(
317 dst: &Path,
318 opt: &RenderOptions,
319 style_files: &[StylePath],
320 css_file_extension: Option<&Path>,
321 resource_suffix: &str,
322) -> Result<(), Error> {
323 if opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
324 for entry in style_files {
326 let theme = entry.basename()?;
327 let extension =
328 try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
329
330 if matches!(theme.as_str(), "light" | "dark" | "ayu") {
332 continue;
333 }
334
335 let bytes = try_err!(fs::read(&entry.path), &entry.path);
336 let filename = format!("{theme}{resource_suffix}.{extension}");
337 let dst_filename = dst.join(filename);
338 try_err!(fs::write(&dst_filename, bytes), &dst_filename);
339 }
340
341 if let Some(css) = css_file_extension {
344 let buffer = try_err!(fs::read_to_string(css), css);
345 let path = static_files::suffix_path("theme.css", resource_suffix);
346 let dst_path = dst.join(path);
347 try_err!(fs::write(&dst_path, buffer), &dst_path);
348 }
349 }
350
351 if opt.emit.contains(&EmitType::HtmlStaticFiles) {
352 let static_dir = dst.join("static.files");
353 try_err!(fs::create_dir_all(&static_dir), &static_dir);
354
355 static_files::for_each(|f: &static_files::StaticFile| {
356 let filename = static_dir.join(f.output_filename());
357 let contents: &[u8] =
358 if opt.disable_minification { f.src_bytes } else { f.minified_bytes };
359 fs::write(&filename, contents).map_err(|e| PathError::new(e, &filename))
360 })?;
361 }
362
363 Ok(())
364}
365
366#[derive(Serialize, Deserialize, Clone, Debug)]
368pub(crate) struct CrateInfo {
369 version: CrateInfoVersion,
370 src_files_js: PartsAndLocations<SourcesPart>,
371 search_index: SerializedSearchIndex,
372 all_crates: PartsAndLocations<AllCratesPart>,
373 crates_index: PartsAndLocations<CratesIndexPart>,
374 trait_impl: PartsAndLocations<TraitAliasPart>,
375 type_impl: PartsAndLocations<TypeAliasPart>,
376}
377
378impl CrateInfo {
379 pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
381 parts_paths
382 .iter()
383 .fold(Ok(Vec::new()), |acc, parts_path| {
384 let mut acc = acc?;
385 let dir = &parts_path.0;
386 let mut files: Vec<Result<PathBuf, std::io::Error>> = try_err!(std::fs::read_dir(dir), dir.as_path())
387 .map(|file| Ok(file?.path()))
388 .collect();
389 files.sort_by_key(|p| p.as_ref().map_or(PathBuf::new(), |p| p.clone()));
390 acc.append(&mut files
391 .into_iter()
392 .filter_map(|file| {
393 let to_crate_info = |file: Result<PathBuf, std::io::Error>| -> Result<Option<CrateInfo>, Error> {
394 let file = try_err!(file, dir.as_path());
395 if file.extension() != Some(OsStr::new("json")) {
396 return Ok(None);
397 }
398 let parts = try_err!(fs::read(&file), &file);
399 let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &file);
400 Ok(Some(parts))
401 };
402 to_crate_info(file).transpose()
403 })
404 .collect::<Result<Vec<CrateInfo>, Error>>()?);
405 Ok(acc)
406 })
407 }
408}
409
410#[derive(Serialize, Deserialize, Clone, Debug)]
418enum CrateInfoVersion {
419 V2,
420}
421
422#[derive(Serialize, Deserialize, Debug, Clone)]
424#[serde(transparent)]
425struct PartsAndLocations<P> {
426 parts: Vec<(PathBuf, P)>,
427}
428
429impl<P> Default for PartsAndLocations<P> {
430 fn default() -> Self {
431 Self { parts: Vec::default() }
432 }
433}
434
435impl<T, U> PartsAndLocations<Part<T, U>> {
436 fn push(&mut self, path: PathBuf, item: U) {
437 self.parts.push((path, Part { _artifact: PhantomData, item }));
438 }
439
440 fn with(path: PathBuf, part: U) -> Self {
442 let mut ret = Self::default();
443 ret.push(path, part);
444 ret
445 }
446}
447
448#[derive(Serialize, Deserialize, Debug, Clone)]
452#[serde(transparent)]
453struct Part<T, U> {
454 #[serde(skip)]
455 _artifact: PhantomData<T>,
456 item: U,
457}
458
459impl<T, U: fmt::Display> fmt::Display for Part<T, U> {
460 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 write!(f, "{}", self.item)
463 }
464}
465
466trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
468 type FileFormat: sorted_template::FileFormat;
470 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
471}
472
473#[derive(Serialize, Deserialize, Clone, Default, Debug)]
474struct AllCrates;
475type AllCratesPart = Part<AllCrates, OrderedJson>;
476impl CciPart for AllCratesPart {
477 type FileFormat = sorted_template::Js;
478 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
479 &crate_info.all_crates
480 }
481}
482
483impl AllCratesPart {
484 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
485 SortedTemplate::from_before_after("window.ALL_CRATES = [", "];")
486 }
487
488 fn get(
489 crate_name_json: OrderedJson,
490 resource_suffix: &str,
491 ) -> Result<PartsAndLocations<Self>, Error> {
492 let path = suffix_path("crates.js", resource_suffix);
495 Ok(PartsAndLocations::with(path, crate_name_json))
496 }
497}
498
499#[derive(Serialize, Deserialize, Clone, Default, Debug)]
501struct AllCratesEntry<'a> {
502 #[serde(rename = "c")]
503 crate_name: Cow<'a, str>,
504
505 #[serde(rename = "h")]
507 #[serde(default)]
508 #[serde(skip_serializing_if = "str::is_empty")]
509 heading: Cow<'a, str>,
510}
511
512fn hack_get_external_crate_names(
519 doc_root: &Path,
520 resource_suffix: &str,
521) -> Result<Vec<String>, Error> {
522 let path = doc_root.join(suffix_path("crates.js", resource_suffix));
523 let Ok(content) = fs::read_to_string(&path) else {
524 return Ok(Vec::default());
526 };
527 if let Some(start) = content.find('[')
530 && let Some(end) = content[start..].find(']')
531 {
532 let content: Vec<AllCratesEntry<'static>> =
533 try_err!(serde_json::from_str(&content[start..=start + end]), &path);
534 let crate_names: Vec<String> =
535 content.into_iter().map(|entry| entry.crate_name.into_owned()).collect();
536 Ok(crate_names)
537 } else {
538 Err(Error::new("could not find crates list in crates.js", path))
539 }
540}
541
542#[derive(Serialize, Deserialize, Clone, Default, Debug)]
543struct CratesIndex;
544type CratesIndexPart = Part<CratesIndex, String>;
545impl CciPart for CratesIndexPart {
546 type FileFormat = sorted_template::Html;
547 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
548 &crate_info.crates_index
549 }
550}
551
552impl CratesIndexPart {
553 fn blank(
554 layout: &layout::Layout,
555 opt: &RenderOptions,
556 style_files: &[StylePath],
557 ) -> SortedTemplate<<Self as CciPart>::FileFormat> {
558 let page = layout::Page {
559 title: "Index of crates",
560 short_title: "Crates",
561 css_class: "mod sys",
562 root_path: "./",
563 static_root_path: opt.static_root_path.as_deref(),
564 description: "List of crates",
565 resource_suffix: &opt.resource_suffix,
566 rust_logo: true,
567 };
568 const DELIMITER: &str = "\u{FFFC}"; let content = format_args!(
570 "<div class=\"main-heading\">\
571 <h1>List of all crates</h1>\
572 <rustdoc-toolbar></rustdoc-toolbar>\
573 </div>\
574 <ul class=\"all-items\">{DELIMITER}</ul>"
575 );
576 let template = layout::render(layout, &page, "", content, style_files);
577 SortedTemplate::from_template(&template, DELIMITER)
578 .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page")
579 }
580
581 fn get(crate_name: &str, external_crates: &[String]) -> Result<PartsAndLocations<Self>, Error> {
583 let mut ret = PartsAndLocations::default();
584 let path = Path::new("index.html");
585 for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) {
586 let part = format!(
587 "<li><a href=\"{trailing_slash}index.html\">{crate_name}</a></li>",
588 trailing_slash = ensure_trailing_slash(crate_name),
589 );
590 ret.push(path.to_path_buf(), part);
591 }
592 Ok(ret)
593 }
594}
595
596#[derive(Serialize, Deserialize, Clone, Default, Debug)]
597struct Sources;
598type SourcesPart = Part<Sources, EscapedJson>;
599impl CciPart for SourcesPart {
600 type FileFormat = sorted_template::Js;
601 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
602 &crate_info.src_files_js
603 }
604}
605
606impl SourcesPart {
607 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
608 SortedTemplate::from_before_after(r"createSrcSidebar('[", r"]');")
612 }
613
614 fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result<PartsAndLocations<Self>, Error> {
615 let hierarchy = Rc::new(Hierarchy::default());
616 cx.shared
617 .local_sources
618 .iter()
619 .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
620 .for_each(|source| hierarchy.add_path(source));
621 let path = suffix_path("src-files.js", &cx.shared.resource_suffix);
622 let hierarchy = hierarchy.to_json_string();
623 let part = OrderedJson::array_unsorted([crate_name, &hierarchy]);
624 let part = EscapedJson::from(part);
625 Ok(PartsAndLocations::with(path, part))
626 }
627}
628
629#[derive(Debug, Default)]
631struct Hierarchy {
632 parent: Weak<Self>,
633 elem: OsString,
634 children: RefCell<FxIndexMap<OsString, Rc<Self>>>,
635 elems: RefCell<FxIndexSet<OsString>>,
636}
637
638impl Hierarchy {
639 fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
640 Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
641 }
642
643 fn to_json_string(&self) -> OrderedJson {
644 let subs = self.children.borrow();
645 let files = self.elems.borrow();
646 let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion"))
647 .unwrap();
648 let mut out = Vec::from([name]);
649 if !subs.is_empty() || !files.is_empty() {
650 let subs = subs.iter().map(|(_, s)| s.to_json_string());
651 out.push(OrderedJson::array_sorted(subs));
652 }
653 if !files.is_empty() {
654 let files = files
655 .iter()
656 .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap());
657 out.push(OrderedJson::array_sorted(files));
658 }
659 OrderedJson::array_unsorted(out)
660 }
661
662 fn add_path(self: &Rc<Self>, path: &Path) {
663 let mut h = Rc::clone(self);
664 let mut components = path
665 .components()
666 .filter(|component| matches!(component, Component::Normal(_) | Component::ParentDir))
667 .peekable();
668
669 assert!(components.peek().is_some(), "empty file path");
670 while let Some(component) = components.next() {
671 match component {
672 Component::Normal(s) => {
673 if components.peek().is_none() {
674 h.elems.borrow_mut().insert(s.to_owned());
675 break;
676 }
677 h = {
678 let mut children = h.children.borrow_mut();
679
680 if let Some(existing) = children.get(s) {
681 Rc::clone(existing)
682 } else {
683 let new_node = Rc::new(Self::with_parent(s.to_owned(), &h));
684 children.insert(s.to_owned(), Rc::clone(&new_node));
685 new_node
686 }
687 };
688 }
689 Component::ParentDir if let Some(parent) = h.parent.upgrade() => {
690 h = parent;
691 }
692 _ => {}
693 }
694 }
695 }
696}
697
698#[derive(Serialize, Deserialize, Clone, Default, Debug)]
699struct TypeAlias;
700type TypeAliasPart = Part<TypeAlias, OrderedJson>;
701impl CciPart for TypeAliasPart {
702 type FileFormat = sorted_template::Js;
703 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
704 &crate_info.type_impl
705 }
706}
707
708impl TypeAliasPart {
709 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
710 SortedTemplate::from_before_after(
711 r"(function() {
712 var type_impls = Object.fromEntries([",
713 r"]);
714 if (window.register_type_impls) {
715 window.register_type_impls(type_impls);
716 } else {
717 window.pending_type_impls = type_impls;
718 }
719})()",
720 )
721 }
722
723 fn get(
724 cx: &mut Context<'_>,
725 krate: &Crate,
726 crate_name_json: &OrderedJson,
727 ) -> Result<PartsAndLocations<Self>, Error> {
728 let mut path_parts = PartsAndLocations::default();
729
730 let mut type_impl_collector = TypeImplCollector {
731 aliased_types: IndexMap::default(),
732 visited_aliases: FxHashSet::default(),
733 cx,
734 };
735 DocVisitor::visit_crate(&mut type_impl_collector, krate);
736 let cx = type_impl_collector.cx;
737 let aliased_types = type_impl_collector.aliased_types;
738 for aliased_type in aliased_types.values() {
739 let impls = aliased_type.impl_.values().filter_map(
740 |AliasedTypeImpl { impl_, type_aliases }| {
741 let mut ret: Option<AliasSerializableImpl> = None;
742 for &(type_alias_fqp, type_alias_item) in type_aliases {
746 cx.id_map.borrow_mut().clear();
747 cx.deref_id_map.borrow_mut().clear();
748 let type_alias_fqp = join_path_syms(type_alias_fqp);
749 if let Some(ret) = &mut ret {
750 ret.aliases.push(type_alias_fqp);
751 } else {
752 let target_trait_did =
753 impl_.inner_impl().trait_.as_ref().map(|trait_| trait_.def_id());
754 let provided_methods;
755 let assoc_link = if let Some(target_trait_did) = target_trait_did {
756 provided_methods =
757 impl_.inner_impl().provided_trait_methods(cx.tcx());
758 AssocItemLink::GotoSource(
759 ItemId::DefId(target_trait_did),
760 &provided_methods,
761 )
762 } else {
763 AssocItemLink::Anchor(None)
764 };
765 let text = super::render_impl(
766 cx,
767 impl_,
768 type_alias_item,
769 assoc_link,
770 RenderMode::Normal,
771 None,
772 &[],
773 ImplRenderingParameters {
774 show_def_docs: true,
775 show_default_items: true,
776 show_non_assoc_items: true,
777 toggle_open_by_default: true,
778 },
779 )
780 .to_string();
781 let trait_ = impl_
783 .inner_impl()
784 .trait_
785 .as_ref()
786 .map(|trait_| format!("{:#}", print_path(trait_, cx)));
787 ret = Some(AliasSerializableImpl {
788 text,
789 trait_,
790 aliases: vec![type_alias_fqp],
791 })
792 }
793 }
794 ret
795 },
796 );
797
798 let mut path = PathBuf::from("type.impl");
799 for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
800 path.push(component.as_str());
801 }
802 let aliased_item_type = aliased_type.target_type;
803 path.push(format!(
804 "{aliased_item_type}.{}.js",
805 aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
806 ));
807
808 let part = OrderedJson::array_sorted(
809 impls.map(|impl_| OrderedJson::serialize(impl_).unwrap()),
810 );
811 path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
812 }
813 Ok(path_parts)
814 }
815}
816
817#[derive(Serialize, Deserialize, Clone, Default, Debug)]
818struct TraitAlias;
819type TraitAliasPart = Part<TraitAlias, OrderedJson>;
820impl CciPart for TraitAliasPart {
821 type FileFormat = sorted_template::Js;
822 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
823 &crate_info.trait_impl
824 }
825}
826
827impl TraitAliasPart {
828 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
829 SortedTemplate::from_before_after(
830 r"(function() {
831 const implementors = Object.fromEntries([",
832 r"]);
833 if (window.register_implementors) {
834 window.register_implementors(implementors);
835 } else {
836 window.pending_implementors = implementors;
837 }
838})()",
839 )
840 }
841
842 fn get(
843 cx: &Context<'_>,
844 crate_name_json: &OrderedJson,
845 ) -> Result<PartsAndLocations<Self>, Error> {
846 let cache = &cx.shared.cache;
847 let mut path_parts = PartsAndLocations::default();
848 for (&did, imps) in &cache.implementors {
851 let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
859 Some(p) => match cache
860 .paths
861 .get(&did)
862 .map(|info| (&info.parts, info.ty))
863 .or_else(|| cache.external_paths.get(&did).map(|(parts, ty)| (parts, *ty)))
864 {
865 Some((_, t)) => (p, t),
866 None => continue,
867 },
868 None => match cache.external_paths.get(&did) {
869 Some((p, t)) => (p, *t),
870 None => continue,
871 },
872 };
873
874 let mut implementors = imps
875 .iter()
876 .filter_map(|imp| {
877 if imp.impl_item.item_id.krate() == did.krate
885 || !imp.impl_item.item_id.is_local()
886 {
887 None
888 } else {
889 let impl_ = imp.inner_impl();
890 let print = print_impl(impl_, false, cx);
891 Some(Implementor {
892 text: format!("{}", print),
893 cmp_text: format!("{:#}", print),
894 synthetic: imp.inner_impl().kind.is_auto(),
895 types: collect_paths_for_type(&imp.inner_impl().for_, cache),
896 is_negative: impl_.is_negative_trait_impl(),
897 })
898 }
899 })
900 .peekable();
901
902 if implementors.peek().is_none() && !cache.paths.contains_key(&did) {
906 continue;
907 }
908
909 let mut path = PathBuf::from("trait.impl");
910 for component in &remote_path[..remote_path.len() - 1] {
911 path.push(component.as_str());
912 }
913 path.push(format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
914
915 let mut implementors = implementors.collect::<Vec<_>>();
916 implementors.sort_unstable_by(|a, b| compare_names(&a.cmp_text, &b.cmp_text));
919
920 path_parts
921 .push(path, OrderedJson::serialize((crate_name_json, &implementors)).unwrap());
922 }
923 Ok(path_parts)
924 }
925}
926
927struct Implementor {
928 text: String,
930 cmp_text: String,
933 synthetic: bool,
934 types: Vec<String>,
935 is_negative: bool,
936}
937
938impl Serialize for Implementor {
939 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
940 where
941 S: Serializer,
942 {
943 let mut seq = serializer.serialize_seq(None)?;
944 seq.serialize_element(&self.text)?;
945 seq.serialize_element(if self.is_negative { &1 } else { &0 })?;
946 if self.synthetic {
947 seq.serialize_element(&1)?;
948 seq.serialize_element(&self.types)?;
949 }
950 seq.end()
951 }
952}
953
954struct TypeImplCollector<'cx, 'cache, 'item> {
962 aliased_types: IndexMap<DefId, AliasedType<'cache, 'item>>,
964 visited_aliases: FxHashSet<DefId>,
965 cx: &'cache Context<'cx>,
966}
967
968struct AliasedType<'cache, 'item> {
984 target_fqp: &'cache [Symbol],
986 target_type: ItemType,
987 impl_: IndexMap<ItemId, AliasedTypeImpl<'cache, 'item>>,
990}
991
992struct AliasedTypeImpl<'cache, 'item> {
997 impl_: &'cache Impl,
998 type_aliases: Vec<(&'cache [Symbol], &'item Item)>,
999}
1000
1001impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> {
1002 fn visit_item(&mut self, it: &'item Item) {
1003 self.visit_item_recur(it);
1004 let cache = &self.cx.shared.cache;
1005 let ItemKind::TypeAlias(ref t) = it.kind else { return };
1006 let Some(self_did) = it.item_id.as_def_id() else { return };
1007 if !self.visited_aliases.insert(self_did) {
1008 return;
1009 }
1010 let Some(target_did) = t.type_.def_id(cache) else { return };
1011 let get_extern =
1012 { || cache.external_paths.get(&target_did).map(|(parts, ty)| (parts, *ty)) };
1013 let Some((target_fqp, target_type)) =
1014 cache.paths.get(&target_did).map(|info| (&info.parts, info.ty)).or_else(get_extern)
1015 else {
1016 return;
1017 };
1018 let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
1019 let impl_ = cache
1020 .impls
1021 .get(&target_did)
1022 .into_iter()
1023 .flatten()
1024 .map(|impl_| {
1025 (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() })
1026 })
1027 .collect();
1028 AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
1029 });
1030 let get_local = { || cache.paths.get(&self_did).map(|info| &info.parts) };
1031 let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
1032 return;
1033 };
1034 let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
1035 let mut seen_impls: FxHashSet<ItemId> =
1039 cache.impls.get(&self_did).into_iter().flatten().map(|i| i.impl_item.item_id).collect();
1040 for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
1041 let Some(impl_did) = impl_item_id.as_def_id() else { continue };
1049 let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
1050 let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx());
1051 if !reject_cx.types_may_unify(aliased_ty, for_ty) {
1052 continue;
1053 }
1054 if !seen_impls.insert(*impl_item_id) {
1056 continue;
1057 }
1058 aliased_type_impl.type_aliases.push((&self_fqp[..], it));
1060 }
1061 }
1062}
1063
1064struct AliasSerializableImpl {
1066 text: String,
1067 trait_: Option<String>,
1068 aliases: Vec<String>,
1069}
1070
1071impl Serialize for AliasSerializableImpl {
1072 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1073 where
1074 S: Serializer,
1075 {
1076 let mut seq = serializer.serialize_seq(None)?;
1077 seq.serialize_element(&self.text)?;
1078 if let Some(trait_) = &self.trait_ {
1079 seq.serialize_element(trait_)?;
1080 } else {
1081 seq.serialize_element(&0)?;
1082 }
1083 for type_ in &self.aliases {
1084 seq.serialize_element(type_)?;
1085 }
1086 seq.end()
1087 }
1088}
1089
1090fn get_path_parts<T: CciPart>(
1091 dst: &Path,
1092 crates_info: &[CrateInfo],
1093) -> FxIndexMap<PathBuf, Vec<String>> {
1094 let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default();
1095 crates_info.iter().flat_map(|crate_info| T::from_crate_info(crate_info).parts.iter()).for_each(
1096 |(path, part)| {
1097 let path = dst.join(path);
1098 let part = part.to_string();
1099 templates.entry(path).or_default().push(part);
1100 },
1101 );
1102 templates
1103}
1104
1105fn create_parents(path: &Path) -> Result<(), Error> {
1107 let parent = path.parent().expect("should not have an empty path here");
1108 try_err!(fs::create_dir_all(parent), parent);
1109 Ok(())
1110}
1111
1112fn read_template_or_blank<F, T: FileFormat>(
1114 mut make_blank: F,
1115 path: &Path,
1116 should_merge: &ShouldMerge,
1117) -> Result<SortedTemplate<T>, Error>
1118where
1119 F: FnMut() -> SortedTemplate<T>,
1120{
1121 if !should_merge.read_rendered_cci {
1122 return Ok(make_blank());
1123 }
1124 match fs::read_to_string(path) {
1125 Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
1126 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
1127 Err(e) => Err(Error::new(e, path)),
1128 }
1129}
1130
1131fn write_rendered_cci<T: CciPart, F>(
1133 mut make_blank: F,
1134 dst: &Path,
1135 crates_info: &[CrateInfo],
1136 should_merge: &ShouldMerge,
1137) -> Result<(), Error>
1138where
1139 F: FnMut() -> SortedTemplate<T::FileFormat>,
1140{
1141 for (path, parts) in get_path_parts::<T>(dst, crates_info) {
1143 create_parents(&path)?;
1144 let mut template =
1146 read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
1147 for part in parts {
1148 template.append(part);
1149 }
1150 let mut file = try_err!(File::create_buffered(&path), &path);
1151 try_err!(write!(file, "{template}"), &path);
1152 try_err!(file.flush(), &path);
1153 }
1154 Ok(())
1155}
1156
1157#[cfg(test)]
1158mod tests;