rustdoc/html/render/
write_shared.rs

1//! Rustdoc writes aut two kinds of shared files:
2//!  - Static files, which are embedded in the rustdoc binary and are written with a
3//!    filename that includes a hash of their contents. These will always have a new
4//!    URL if the contents change, so they are safe to cache with the
5//!    `Cache-Control: immutable` directive. They are written under the static.files/
6//!    directory and are written when --emit-type is empty (default) or contains
7//!    "toolchain-specific". If using the --static-root-path flag, it should point
8//!    to a URL path prefix where each of these filenames can be fetched.
9//!  - Invocation specific files. These are generated based on the crate(s) being
10//!    documented. Their filenames need to be predictable without knowing their
11//!    contents, so they do not include a hash in their filename and are not safe to
12//!    cache with `Cache-Control: immutable`. They include the contents of the
13//!    --resource-suffix flag and are emitted when --emit-type is empty (default)
14//!    or contains "invocation-specific".
15
16use std::cell::RefCell;
17use std::ffi::{OsStr, OsString};
18use std::fs::File;
19use std::io::{self, Write as _};
20use std::iter::once;
21use std::marker::PhantomData;
22use std::path::{Component, Path, PathBuf};
23use std::rc::{Rc, Weak};
24use std::str::FromStr;
25use std::{fmt, fs};
26
27use indexmap::IndexMap;
28use regex::Regex;
29use rustc_ast::join_path_syms;
30use rustc_data_structures::flock;
31use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
32use rustc_middle::ty::TyCtxt;
33use rustc_middle::ty::fast_reject::DeepRejectCtxt;
34use rustc_span::Symbol;
35use rustc_span::def_id::DefId;
36use serde::de::DeserializeOwned;
37use serde::ser::SerializeSeq;
38use serde::{Deserialize, Serialize, Serializer};
39
40use super::{Context, RenderMode, collect_paths_for_type, ensure_trailing_slash};
41use crate::clean::{Crate, Item, ItemId, ItemKind};
42use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
43use crate::docfs::PathError;
44use crate::error::Error;
45use crate::formats::Impl;
46use crate::formats::item_type::ItemType;
47use crate::html::format::{print_impl, print_path};
48use crate::html::layout;
49use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
50use crate::html::render::search_index::{SerializedSearchIndex, build_index};
51use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
52use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
53use crate::html::static_files::{self, suffix_path};
54use crate::visit::DocVisitor;
55use crate::{try_err, try_none};
56
57pub(crate) fn write_shared(
58    cx: &mut Context<'_>,
59    krate: &Crate,
60    opt: &RenderOptions,
61    tcx: TyCtxt<'_>,
62) -> Result<(), Error> {
63    // NOTE(EtomicBomb): I don't think we need sync here because no read-after-write?
64    cx.shared.fs.set_sync_only(true);
65    let lock_file = cx.dst.join(".lock");
66    // Write shared runs within a flock; disable thread dispatching of IO temporarily.
67    let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
68
69    let search_index =
70        build_index(krate, &mut cx.shared.cache, tcx, &cx.dst, &cx.shared.resource_suffix)?;
71
72    let crate_name = krate.name(cx.tcx());
73    let crate_name = crate_name.as_str(); // rand
74    let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
75    let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
76    let info = CrateInfo {
77        version: CrateInfoVersion::V2,
78        src_files_js: SourcesPart::get(cx, &crate_name_json)?,
79        search_index,
80        all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
81        crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
82        trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
83        type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
84    };
85
86    if let Some(parts_out_dir) = &opt.parts_out_dir {
87        let mut parts_out_file = parts_out_dir.0.clone();
88        parts_out_file.push(&format!("{crate_name}.json"));
89        create_parents(&parts_out_file)?;
90        try_err!(
91            fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()),
92            &parts_out_dir.0
93        );
94    }
95
96    let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
97    crates.push(info);
98
99    if opt.should_merge.write_rendered_cci {
100        write_not_crate_specific(
101            &crates,
102            &cx.dst,
103            opt,
104            &cx.shared.style_files,
105            cx.shared.layout.css_file_extension.as_deref(),
106            &cx.shared.resource_suffix,
107            cx.info.include_sources,
108        )?;
109        match &opt.index_page {
110            Some(index_page) if opt.enable_index_page => {
111                let mut md_opts = opt.clone();
112                md_opts.output = cx.dst.clone();
113                md_opts.external_html = cx.shared.layout.external_html.clone();
114                try_err!(
115                    crate::markdown::render_and_write(index_page, md_opts, cx.shared.edition()),
116                    &index_page
117                );
118            }
119            None if opt.enable_index_page => {
120                write_rendered_cci::<CratesIndexPart, _>(
121                    || CratesIndexPart::blank(cx),
122                    &cx.dst,
123                    &crates,
124                    &opt.should_merge,
125                )?;
126            }
127            _ => {} // they don't want an index page
128        }
129    }
130
131    cx.shared.fs.set_sync_only(false);
132    Ok(())
133}
134
135/// Writes files that are written directly to the `--out-dir`, without the prefix from the current
136/// crate. These are the rendered cross-crate files that encode info from multiple crates (e.g.
137/// search index), and the static files.
138pub(crate) fn write_not_crate_specific(
139    crates: &[CrateInfo],
140    dst: &Path,
141    opt: &RenderOptions,
142    style_files: &[StylePath],
143    css_file_extension: Option<&Path>,
144    resource_suffix: &str,
145    include_sources: bool,
146) -> Result<(), Error> {
147    write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
148    write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
149    Ok(())
150}
151
152fn write_rendered_cross_crate_info(
153    crates: &[CrateInfo],
154    dst: &Path,
155    opt: &RenderOptions,
156    include_sources: bool,
157    resource_suffix: &str,
158) -> Result<(), Error> {
159    let m = &opt.should_merge;
160    if opt.should_emit_crate() {
161        if include_sources {
162            write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
163        }
164        crates
165            .iter()
166            .fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
167            .sort()
168            .write_to(dst, resource_suffix)?;
169        write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
170    }
171    write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
172    write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, crates, m)?;
173    Ok(())
174}
175
176/// Writes the static files, the style files, and the css extensions.
177/// Have to be careful about these, because they write to the root out dir.
178fn write_static_files(
179    dst: &Path,
180    opt: &RenderOptions,
181    style_files: &[StylePath],
182    css_file_extension: Option<&Path>,
183    resource_suffix: &str,
184) -> Result<(), Error> {
185    let static_dir = dst.join("static.files");
186    try_err!(fs::create_dir_all(&static_dir), &static_dir);
187
188    // Handle added third-party themes
189    for entry in style_files {
190        let theme = entry.basename()?;
191        let extension =
192            try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
193
194        // Skip the official themes. They are written below as part of STATIC_FILES_LIST.
195        if matches!(theme.as_str(), "light" | "dark" | "ayu") {
196            continue;
197        }
198
199        let bytes = try_err!(fs::read(&entry.path), &entry.path);
200        let filename = format!("{theme}{resource_suffix}.{extension}");
201        let dst_filename = dst.join(filename);
202        try_err!(fs::write(&dst_filename, bytes), &dst_filename);
203    }
204
205    // When the user adds their own CSS files with --extend-css, we write that as an
206    // invocation-specific file (that is, with a resource suffix).
207    if let Some(css) = css_file_extension {
208        let buffer = try_err!(fs::read_to_string(css), css);
209        let path = static_files::suffix_path("theme.css", resource_suffix);
210        let dst_path = dst.join(path);
211        try_err!(fs::write(&dst_path, buffer), &dst_path);
212    }
213
214    if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
215        static_files::for_each(|f: &static_files::StaticFile| {
216            let filename = static_dir.join(f.output_filename());
217            let contents: &[u8] =
218                if opt.disable_minification { f.src_bytes } else { f.minified_bytes };
219            fs::write(&filename, contents).map_err(|e| PathError::new(e, &filename))
220        })?;
221    }
222
223    Ok(())
224}
225
226/// Contains pre-rendered contents to insert into the CCI template
227#[derive(Serialize, Deserialize, Clone, Debug)]
228pub(crate) struct CrateInfo {
229    version: CrateInfoVersion,
230    src_files_js: PartsAndLocations<SourcesPart>,
231    search_index: SerializedSearchIndex,
232    all_crates: PartsAndLocations<AllCratesPart>,
233    crates_index: PartsAndLocations<CratesIndexPart>,
234    trait_impl: PartsAndLocations<TraitAliasPart>,
235    type_impl: PartsAndLocations<TypeAliasPart>,
236}
237
238impl CrateInfo {
239    /// Read all of the crate info from its location on the filesystem
240    pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
241        parts_paths
242            .iter()
243            .fold(Ok(Vec::new()), |acc, parts_path| {
244                let mut acc = acc?;
245                let dir = &parts_path.0;
246                acc.append(&mut try_err!(std::fs::read_dir(dir), dir.as_path())
247                    .filter_map(|file| {
248                        let to_crate_info = |file: Result<std::fs::DirEntry, std::io::Error>| -> Result<Option<CrateInfo>, Error> {
249                            let file = try_err!(file, dir.as_path());
250                            if file.path().extension() != Some(OsStr::new("json")) {
251                                return Ok(None);
252                            }
253                            let parts = try_err!(fs::read(file.path()), file.path());
254                            let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), file.path());
255                            Ok(Some(parts))
256                        };
257                        to_crate_info(file).transpose()
258                    })
259                    .collect::<Result<Vec<CrateInfo>, Error>>()?);
260                Ok(acc)
261            })
262    }
263}
264
265/// Version for the format of the crate-info file.
266///
267/// This enum should only ever have one variant, representing the current version.
268/// Gives pretty good error message about expecting the current version on deserialize.
269///
270/// Must be incremented (V2, V3, etc.) upon any changes to the search index or CrateInfo,
271/// to provide better diagnostics about including an invalid file.
272#[derive(Serialize, Deserialize, Clone, Debug)]
273enum CrateInfoVersion {
274    V2,
275}
276
277/// Paths (relative to the doc root) and their pre-merge contents
278#[derive(Serialize, Deserialize, Debug, Clone)]
279#[serde(transparent)]
280struct PartsAndLocations<P> {
281    parts: Vec<(PathBuf, P)>,
282}
283
284impl<P> Default for PartsAndLocations<P> {
285    fn default() -> Self {
286        Self { parts: Vec::default() }
287    }
288}
289
290impl<T, U> PartsAndLocations<Part<T, U>> {
291    fn push(&mut self, path: PathBuf, item: U) {
292        self.parts.push((path, Part { _artifact: PhantomData, item }));
293    }
294
295    /// Singleton part, one file
296    fn with(path: PathBuf, part: U) -> Self {
297        let mut ret = Self::default();
298        ret.push(path, part);
299        ret
300    }
301}
302
303/// A piece of one of the shared artifacts for documentation (search index, sources, alias list, etc.)
304///
305/// Merged at a user specified time and written to the `doc/` directory
306#[derive(Serialize, Deserialize, Debug, Clone)]
307#[serde(transparent)]
308struct Part<T, U> {
309    #[serde(skip)]
310    _artifact: PhantomData<T>,
311    item: U,
312}
313
314impl<T, U: fmt::Display> fmt::Display for Part<T, U> {
315    /// Writes serialized JSON
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        write!(f, "{}", self.item)
318    }
319}
320
321/// Wrapper trait for `Part<T, U>`
322trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
323    /// Identifies the file format of the cross-crate information
324    type FileFormat: sorted_template::FileFormat;
325    fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
326}
327
328#[derive(Serialize, Deserialize, Clone, Default, Debug)]
329struct AllCrates;
330type AllCratesPart = Part<AllCrates, OrderedJson>;
331impl CciPart for AllCratesPart {
332    type FileFormat = sorted_template::Js;
333    fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
334        &crate_info.all_crates
335    }
336}
337
338impl AllCratesPart {
339    fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
340        SortedTemplate::from_before_after("window.ALL_CRATES = [", "];")
341    }
342
343    fn get(
344        crate_name_json: OrderedJson,
345        resource_suffix: &str,
346    ) -> Result<PartsAndLocations<Self>, Error> {
347        // external hack_get_external_crate_names not needed here, because
348        // there's no way that we write the search index but not crates.js
349        let path = suffix_path("crates.js", resource_suffix);
350        Ok(PartsAndLocations::with(path, crate_name_json))
351    }
352}
353
354/// Reads `crates.js`, which seems like the best
355/// place to obtain the list of externally documented crates if the index
356/// page was disabled when documenting the deps.
357///
358/// This is to match the current behavior of rustdoc, which allows you to get all crates
359/// on the index page, even if --enable-index-page is only passed to the last crate.
360fn hack_get_external_crate_names(
361    doc_root: &Path,
362    resource_suffix: &str,
363) -> Result<Vec<String>, Error> {
364    let path = doc_root.join(suffix_path("crates.js", resource_suffix));
365    let Ok(content) = fs::read_to_string(&path) else {
366        // they didn't emit invocation specific, so we just say there were no crates
367        return Ok(Vec::default());
368    };
369    // this is only run once so it's fine not to cache it
370    // !dot_matches_new_line: all crates on same line. greedy: match last bracket
371    let regex = Regex::new(r"\[.*\]").unwrap();
372    let Some(content) = regex.find(&content) else {
373        return Err(Error::new("could not find crates list in crates.js", path));
374    };
375    let content: Vec<String> = try_err!(serde_json::from_str(content.as_str()), &path);
376    Ok(content)
377}
378
379#[derive(Serialize, Deserialize, Clone, Default, Debug)]
380struct CratesIndex;
381type CratesIndexPart = Part<CratesIndex, String>;
382impl CciPart for CratesIndexPart {
383    type FileFormat = sorted_template::Html;
384    fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
385        &crate_info.crates_index
386    }
387}
388
389impl CratesIndexPart {
390    fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
391        let page = layout::Page {
392            title: "Index of crates",
393            short_title: "Crates",
394            css_class: "mod sys",
395            root_path: "./",
396            static_root_path: cx.shared.static_root_path.as_deref(),
397            description: "List of crates",
398            resource_suffix: &cx.shared.resource_suffix,
399            rust_logo: true,
400        };
401        let layout = &cx.shared.layout;
402        let style_files = &cx.shared.style_files;
403        const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this
404        let content = format!(
405            "<div class=\"main-heading\">\
406                <h1>List of all crates</h1>\
407                <rustdoc-toolbar></rustdoc-toolbar>\
408            </div>\
409            <ul class=\"all-items\">{DELIMITER}</ul>"
410        );
411        let template = layout::render(layout, &page, "", content, style_files);
412        SortedTemplate::from_template(&template, DELIMITER)
413            .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page")
414    }
415
416    /// Might return parts that are duplicate with ones in preexisting index.html
417    fn get(crate_name: &str, external_crates: &[String]) -> Result<PartsAndLocations<Self>, Error> {
418        let mut ret = PartsAndLocations::default();
419        let path = Path::new("index.html");
420        for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) {
421            let part = format!(
422                "<li><a href=\"{trailing_slash}index.html\">{crate_name}</a></li>",
423                trailing_slash = ensure_trailing_slash(crate_name),
424            );
425            ret.push(path.to_path_buf(), part);
426        }
427        Ok(ret)
428    }
429}
430
431#[derive(Serialize, Deserialize, Clone, Default, Debug)]
432struct Sources;
433type SourcesPart = Part<Sources, EscapedJson>;
434impl CciPart for SourcesPart {
435    type FileFormat = sorted_template::Js;
436    fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
437        &crate_info.src_files_js
438    }
439}
440
441impl SourcesPart {
442    fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
443        // This needs to be `var`, not `const`.
444        // This variable needs declared in the current global scope so that if
445        // src-script.js loads first, it can pick it up.
446        SortedTemplate::from_before_after(r"createSrcSidebar('[", r"]');")
447    }
448
449    fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result<PartsAndLocations<Self>, Error> {
450        let hierarchy = Rc::new(Hierarchy::default());
451        cx.shared
452            .local_sources
453            .iter()
454            .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
455            .for_each(|source| hierarchy.add_path(source));
456        let path = suffix_path("src-files.js", &cx.shared.resource_suffix);
457        let hierarchy = hierarchy.to_json_string();
458        let part = OrderedJson::array_unsorted([crate_name, &hierarchy]);
459        let part = EscapedJson::from(part);
460        Ok(PartsAndLocations::with(path, part))
461    }
462}
463
464/// Source files directory tree
465#[derive(Debug, Default)]
466struct Hierarchy {
467    parent: Weak<Self>,
468    elem: OsString,
469    children: RefCell<FxIndexMap<OsString, Rc<Self>>>,
470    elems: RefCell<FxIndexSet<OsString>>,
471}
472
473impl Hierarchy {
474    fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
475        Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
476    }
477
478    fn to_json_string(&self) -> OrderedJson {
479        let subs = self.children.borrow();
480        let files = self.elems.borrow();
481        let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion"))
482            .unwrap();
483        let mut out = Vec::from([name]);
484        if !subs.is_empty() || !files.is_empty() {
485            let subs = subs.iter().map(|(_, s)| s.to_json_string());
486            out.push(OrderedJson::array_sorted(subs));
487        }
488        if !files.is_empty() {
489            let files = files
490                .iter()
491                .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap());
492            out.push(OrderedJson::array_sorted(files));
493        }
494        OrderedJson::array_unsorted(out)
495    }
496
497    fn add_path(self: &Rc<Self>, path: &Path) {
498        let mut h = Rc::clone(self);
499        let mut elems = path
500            .components()
501            .filter_map(|s| match s {
502                Component::Normal(s) => Some(s.to_owned()),
503                Component::ParentDir => Some(OsString::from("..")),
504                _ => None,
505            })
506            .peekable();
507        loop {
508            let cur_elem = elems.next().expect("empty file path");
509            if cur_elem == ".." {
510                if let Some(parent) = h.parent.upgrade() {
511                    h = parent;
512                }
513                continue;
514            }
515            if elems.peek().is_none() {
516                h.elems.borrow_mut().insert(cur_elem);
517                break;
518            } else {
519                let entry = Rc::clone(
520                    h.children
521                        .borrow_mut()
522                        .entry(cur_elem.clone())
523                        .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))),
524                );
525                h = entry;
526            }
527        }
528    }
529}
530
531#[derive(Serialize, Deserialize, Clone, Default, Debug)]
532struct TypeAlias;
533type TypeAliasPart = Part<TypeAlias, OrderedJson>;
534impl CciPart for TypeAliasPart {
535    type FileFormat = sorted_template::Js;
536    fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
537        &crate_info.type_impl
538    }
539}
540
541impl TypeAliasPart {
542    fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
543        SortedTemplate::from_before_after(
544            r"(function() {
545    var type_impls = Object.fromEntries([",
546            r"]);
547    if (window.register_type_impls) {
548        window.register_type_impls(type_impls);
549    } else {
550        window.pending_type_impls = type_impls;
551    }
552})()",
553        )
554    }
555
556    fn get(
557        cx: &mut Context<'_>,
558        krate: &Crate,
559        crate_name_json: &OrderedJson,
560    ) -> Result<PartsAndLocations<Self>, Error> {
561        let mut path_parts = PartsAndLocations::default();
562
563        let mut type_impl_collector = TypeImplCollector {
564            aliased_types: IndexMap::default(),
565            visited_aliases: FxHashSet::default(),
566            cx,
567        };
568        DocVisitor::visit_crate(&mut type_impl_collector, krate);
569        let cx = type_impl_collector.cx;
570        let aliased_types = type_impl_collector.aliased_types;
571        for aliased_type in aliased_types.values() {
572            let impls = aliased_type.impl_.values().filter_map(
573                |AliasedTypeImpl { impl_, type_aliases }| {
574                    let mut ret: Option<AliasSerializableImpl> = None;
575                    // render_impl will filter out "impossible-to-call" methods
576                    // to make that functionality work here, it needs to be called with
577                    // each type alias, and if it gives a different result, split the impl
578                    for &(type_alias_fqp, type_alias_item) in type_aliases {
579                        cx.id_map.borrow_mut().clear();
580                        cx.deref_id_map.borrow_mut().clear();
581                        let type_alias_fqp = join_path_syms(type_alias_fqp);
582                        if let Some(ret) = &mut ret {
583                            ret.aliases.push(type_alias_fqp);
584                        } else {
585                            let target_trait_did =
586                                impl_.inner_impl().trait_.as_ref().map(|trait_| trait_.def_id());
587                            let provided_methods;
588                            let assoc_link = if let Some(target_trait_did) = target_trait_did {
589                                provided_methods =
590                                    impl_.inner_impl().provided_trait_methods(cx.tcx());
591                                AssocItemLink::GotoSource(
592                                    ItemId::DefId(target_trait_did),
593                                    &provided_methods,
594                                )
595                            } else {
596                                AssocItemLink::Anchor(None)
597                            };
598                            let text = super::render_impl(
599                                cx,
600                                impl_,
601                                type_alias_item,
602                                assoc_link,
603                                RenderMode::Normal,
604                                None,
605                                &[],
606                                ImplRenderingParameters {
607                                    show_def_docs: true,
608                                    show_default_items: true,
609                                    show_non_assoc_items: true,
610                                    toggle_open_by_default: true,
611                                },
612                            )
613                            .to_string();
614                            // The alternate display prints it as plaintext instead of HTML.
615                            let trait_ = impl_
616                                .inner_impl()
617                                .trait_
618                                .as_ref()
619                                .map(|trait_| format!("{:#}", print_path(trait_, cx)));
620                            ret = Some(AliasSerializableImpl {
621                                text,
622                                trait_,
623                                aliases: vec![type_alias_fqp],
624                            })
625                        }
626                    }
627                    ret
628                },
629            );
630
631            let mut path = PathBuf::from("type.impl");
632            for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
633                path.push(component.as_str());
634            }
635            let aliased_item_type = aliased_type.target_type;
636            path.push(format!(
637                "{aliased_item_type}.{}.js",
638                aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
639            ));
640
641            let part = OrderedJson::array_sorted(
642                impls.map(|impl_| OrderedJson::serialize(impl_).unwrap()),
643            );
644            path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
645        }
646        Ok(path_parts)
647    }
648}
649
650#[derive(Serialize, Deserialize, Clone, Default, Debug)]
651struct TraitAlias;
652type TraitAliasPart = Part<TraitAlias, OrderedJson>;
653impl CciPart for TraitAliasPart {
654    type FileFormat = sorted_template::Js;
655    fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
656        &crate_info.trait_impl
657    }
658}
659
660impl TraitAliasPart {
661    fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
662        SortedTemplate::from_before_after(
663            r"(function() {
664    var implementors = Object.fromEntries([",
665            r"]);
666    if (window.register_implementors) {
667        window.register_implementors(implementors);
668    } else {
669        window.pending_implementors = implementors;
670    }
671})()",
672        )
673    }
674
675    fn get(
676        cx: &Context<'_>,
677        crate_name_json: &OrderedJson,
678    ) -> Result<PartsAndLocations<Self>, Error> {
679        let cache = &cx.shared.cache;
680        let mut path_parts = PartsAndLocations::default();
681        // Update the list of all implementors for traits
682        // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+trait.impl&type=code>
683        for (&did, imps) in &cache.implementors {
684            // Private modules can leak through to this phase of rustdoc, which
685            // could contain implementations for otherwise private types. In some
686            // rare cases we could find an implementation for an item which wasn't
687            // indexed, so we just skip this step in that case.
688            //
689            // FIXME: this is a vague explanation for why this can't be a `get`, in
690            //        theory it should be...
691            let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
692                Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
693                    Some((_, t)) => (p, t),
694                    None => continue,
695                },
696                None => match cache.external_paths.get(&did) {
697                    Some((p, t)) => (p, t),
698                    None => continue,
699                },
700            };
701
702            let mut implementors = imps
703                .iter()
704                .filter_map(|imp| {
705                    // If the trait and implementation are in the same crate, then
706                    // there's no need to emit information about it (there's inlining
707                    // going on). If they're in different crates then the crate defining
708                    // the trait will be interested in our implementation.
709                    //
710                    // If the implementation is from another crate then that crate
711                    // should add it.
712                    if imp.impl_item.item_id.krate() == did.krate
713                        || !imp.impl_item.item_id.is_local()
714                    {
715                        None
716                    } else {
717                        Some(Implementor {
718                            text: print_impl(imp.inner_impl(), false, cx).to_string(),
719                            synthetic: imp.inner_impl().kind.is_auto(),
720                            types: collect_paths_for_type(&imp.inner_impl().for_, cache),
721                        })
722                    }
723                })
724                .peekable();
725
726            // Only create a js file if we have impls to add to it. If the trait is
727            // documented locally though we always create the file to avoid dead
728            // links.
729            if implementors.peek().is_none() && !cache.paths.contains_key(&did) {
730                continue;
731            }
732
733            let mut path = PathBuf::from("trait.impl");
734            for component in &remote_path[..remote_path.len() - 1] {
735                path.push(component.as_str());
736            }
737            path.push(format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
738
739            let part = OrderedJson::array_sorted(
740                implementors.map(|implementor| OrderedJson::serialize(implementor).unwrap()),
741            );
742            path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
743        }
744        Ok(path_parts)
745    }
746}
747
748struct Implementor {
749    text: String,
750    synthetic: bool,
751    types: Vec<String>,
752}
753
754impl Serialize for Implementor {
755    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
756    where
757        S: Serializer,
758    {
759        let mut seq = serializer.serialize_seq(None)?;
760        seq.serialize_element(&self.text)?;
761        if self.synthetic {
762            seq.serialize_element(&1)?;
763            seq.serialize_element(&self.types)?;
764        }
765        seq.end()
766    }
767}
768
769/// Collect the list of aliased types and their aliases.
770/// <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+type.impl&type=code>
771///
772/// The clean AST has type aliases that point at their types, but
773/// this visitor works to reverse that: `aliased_types` is a map
774/// from target to the aliases that reference it, and each one
775/// will generate one file.
776struct TypeImplCollector<'cx, 'cache, 'item> {
777    /// Map from DefId-of-aliased-type to its data.
778    aliased_types: IndexMap<DefId, AliasedType<'cache, 'item>>,
779    visited_aliases: FxHashSet<DefId>,
780    cx: &'cache Context<'cx>,
781}
782
783/// Data for an aliased type.
784///
785/// In the final file, the format will be roughly:
786///
787/// ```json
788/// // type.impl/CRATE/TYPENAME.js
789/// JSONP(
790/// "CRATE": [
791///   ["IMPL1 HTML", "ALIAS1", "ALIAS2", ...],
792///   ["IMPL2 HTML", "ALIAS3", "ALIAS4", ...],
793///    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ struct AliasedType
794///   ...
795/// ]
796/// )
797/// ```
798struct AliasedType<'cache, 'item> {
799    /// This is used to generate the actual filename of this aliased type.
800    target_fqp: &'cache [Symbol],
801    target_type: ItemType,
802    /// This is the data stored inside the file.
803    /// ItemId is used to deduplicate impls.
804    impl_: IndexMap<ItemId, AliasedTypeImpl<'cache, 'item>>,
805}
806
807/// The `impl_` contains data that's used to figure out if an alias will work,
808/// and to generate the HTML at the end.
809///
810/// The `type_aliases` list is built up with each type alias that matches.
811struct AliasedTypeImpl<'cache, 'item> {
812    impl_: &'cache Impl,
813    type_aliases: Vec<(&'cache [Symbol], &'item Item)>,
814}
815
816impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> {
817    fn visit_item(&mut self, it: &'item Item) {
818        self.visit_item_recur(it);
819        let cache = &self.cx.shared.cache;
820        let ItemKind::TypeAliasItem(ref t) = it.kind else { return };
821        let Some(self_did) = it.item_id.as_def_id() else { return };
822        if !self.visited_aliases.insert(self_did) {
823            return;
824        }
825        let Some(target_did) = t.type_.def_id(cache) else { return };
826        let get_extern = { || cache.external_paths.get(&target_did) };
827        let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern)
828        else {
829            return;
830        };
831        let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
832            let impl_ = cache
833                .impls
834                .get(&target_did)
835                .into_iter()
836                .flatten()
837                .map(|impl_| {
838                    (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() })
839                })
840                .collect();
841            AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
842        });
843        let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) };
844        let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
845            return;
846        };
847        let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
848        // Exclude impls that are directly on this type. They're already in the HTML.
849        // Some inlining scenarios can cause there to be two versions of the same
850        // impl: one on the type alias and one on the underlying target type.
851        let mut seen_impls: FxHashSet<ItemId> =
852            cache.impls.get(&self_did).into_iter().flatten().map(|i| i.impl_item.item_id).collect();
853        for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
854            // Only include this impl if it actually unifies with this alias.
855            // Synthetic impls are not included; those are also included in the HTML.
856            //
857            // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this
858            // to use type unification.
859            // Be aware of `tests/rustdoc/type-alias/deeply-nested-112515.rs` which might regress.
860            let Some(impl_did) = impl_item_id.as_def_id() else { continue };
861            let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
862            let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx());
863            if !reject_cx.types_may_unify(aliased_ty, for_ty) {
864                continue;
865            }
866            // Avoid duplicates
867            if !seen_impls.insert(*impl_item_id) {
868                continue;
869            }
870            // This impl was not found in the set of rejected impls
871            aliased_type_impl.type_aliases.push((&self_fqp[..], it));
872        }
873    }
874}
875
876/// Final serialized form of the alias impl
877struct AliasSerializableImpl {
878    text: String,
879    trait_: Option<String>,
880    aliases: Vec<String>,
881}
882
883impl Serialize for AliasSerializableImpl {
884    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
885    where
886        S: Serializer,
887    {
888        let mut seq = serializer.serialize_seq(None)?;
889        seq.serialize_element(&self.text)?;
890        if let Some(trait_) = &self.trait_ {
891            seq.serialize_element(trait_)?;
892        } else {
893            seq.serialize_element(&0)?;
894        }
895        for type_ in &self.aliases {
896            seq.serialize_element(type_)?;
897        }
898        seq.end()
899    }
900}
901
902fn get_path_parts<T: CciPart>(
903    dst: &Path,
904    crates_info: &[CrateInfo],
905) -> FxIndexMap<PathBuf, Vec<String>> {
906    let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default();
907    crates_info.iter().flat_map(|crate_info| T::from_crate_info(crate_info).parts.iter()).for_each(
908        |(path, part)| {
909            let path = dst.join(path);
910            let part = part.to_string();
911            templates.entry(path).or_default().push(part);
912        },
913    );
914    templates
915}
916
917/// Create all parents
918fn create_parents(path: &Path) -> Result<(), Error> {
919    let parent = path.parent().expect("should not have an empty path here");
920    try_err!(fs::create_dir_all(parent), parent);
921    Ok(())
922}
923
924/// Returns a blank template unless we could find one to append to
925fn read_template_or_blank<F, T: FileFormat>(
926    mut make_blank: F,
927    path: &Path,
928    should_merge: &ShouldMerge,
929) -> Result<SortedTemplate<T>, Error>
930where
931    F: FnMut() -> SortedTemplate<T>,
932{
933    if !should_merge.read_rendered_cci {
934        return Ok(make_blank());
935    }
936    match fs::read_to_string(path) {
937        Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
938        Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
939        Err(e) => Err(Error::new(e, path)),
940    }
941}
942
943/// info from this crate and the --include-info-json'd crates
944fn write_rendered_cci<T: CciPart, F>(
945    mut make_blank: F,
946    dst: &Path,
947    crates_info: &[CrateInfo],
948    should_merge: &ShouldMerge,
949) -> Result<(), Error>
950where
951    F: FnMut() -> SortedTemplate<T::FileFormat>,
952{
953    // write the merged cci to disk
954    for (path, parts) in get_path_parts::<T>(dst, crates_info) {
955        create_parents(&path)?;
956        // read previous rendered cci from storage, append to them
957        let mut template =
958            read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
959        for part in parts {
960            template.append(part);
961        }
962        let mut file = try_err!(File::create_buffered(&path), &path);
963        try_err!(write!(file, "{template}"), &path);
964        try_err!(file.flush(), &path);
965    }
966    Ok(())
967}
968
969#[cfg(test)]
970mod tests;