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