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