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