use std::cell::RefCell;
use std::ffi::OsString;
use std::fs::File;
use std::io::{self, Write as _};
use std::iter::once;
use std::marker::PhantomData;
use std::path::{Component, Path, PathBuf};
use std::rc::{Rc, Weak};
use std::str::FromStr;
use std::{fmt, fs};
use indexmap::IndexMap;
use itertools::Itertools;
use regex::Regex;
use rustc_data_structures::flock;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
use rustc_span::Symbol;
use rustc_span::def_id::DefId;
use serde::de::DeserializeOwned;
use serde::ser::SerializeSeq;
use serde::{Deserialize, Serialize, Serializer};
use super::{Context, RenderMode, collect_paths_for_type, ensure_trailing_slash};
use crate::clean::{Crate, Item, ItemId, ItemKind};
use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
use crate::docfs::PathError;
use crate::error::Error;
use crate::formats::Impl;
use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
use crate::html::format::Buffer;
use crate::html::layout;
use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
use crate::html::render::search_index::{SerializedSearchIndex, build_index};
use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
use crate::html::static_files::{self, suffix_path};
use crate::visit::DocVisitor;
use crate::{try_err, try_none};
pub(crate) fn write_shared(
cx: &mut Context<'_>,
krate: &Crate,
opt: &RenderOptions,
tcx: TyCtxt<'_>,
) -> Result<(), Error> {
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
let lock_file = cx.dst.join(".lock");
let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
let SerializedSearchIndex { index, desc } =
build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
write_search_desc(cx, &krate, &desc)?; let crate_name = krate.name(cx.tcx());
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)?;
let info = CrateInfo {
version: CrateInfoVersion::V1,
src_files_js: SourcesPart::get(cx, &crate_name_json)?,
search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
crates_index: CratesIndexPart::get(&crate_name, &external_crates)?,
trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
};
if let Some(parts_out_dir) = &opt.parts_out_dir {
create_parents(&parts_out_dir.0)?;
try_err!(
fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
&parts_out_dir.0
);
}
let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
crates.push(info);
if opt.should_merge.write_rendered_cci {
write_not_crate_specific(
&crates,
&cx.dst,
opt,
&cx.shared.style_files,
cx.shared.layout.css_file_extension.as_deref(),
&cx.shared.resource_suffix,
cx.include_sources,
)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
let mut md_opts = opt.clone();
md_opts.output = cx.dst.clone();
md_opts.external_html = cx.shared.layout.external_html.clone();
try_err!(
crate::markdown::render(&index_page, md_opts, cx.shared.edition()),
&index_page
);
}
None if opt.enable_index_page => {
write_rendered_cci::<CratesIndexPart, _>(
|| CratesIndexPart::blank(cx),
&cx.dst,
&crates,
&opt.should_merge,
)?;
}
_ => {} }
}
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
Ok(())
}
pub(crate) fn write_not_crate_specific(
crates: &[CrateInfo],
dst: &Path,
opt: &RenderOptions,
style_files: &[StylePath],
css_file_extension: Option<&Path>,
resource_suffix: &str,
include_sources: bool,
) -> Result<(), Error> {
write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
Ok(())
}
fn write_rendered_cross_crate_info(
crates: &[CrateInfo],
dst: &Path,
opt: &RenderOptions,
include_sources: bool,
) -> Result<(), Error> {
let m = &opt.should_merge;
if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
if include_sources {
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates, m)?;
}
write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates, m)?;
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates, m)?;
}
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates, m)?;
write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates, m)?;
Ok(())
}
fn write_static_files(
dst: &Path,
opt: &RenderOptions,
style_files: &[StylePath],
css_file_extension: Option<&Path>,
resource_suffix: &str,
) -> Result<(), Error> {
let static_dir = dst.join("static.files");
try_err!(fs::create_dir_all(&static_dir), &static_dir);
for entry in style_files {
let theme = entry.basename()?;
let extension =
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
if matches!(theme.as_str(), "light" | "dark" | "ayu") {
continue;
}
let bytes = try_err!(fs::read(&entry.path), &entry.path);
let filename = format!("{theme}{resource_suffix}.{extension}");
let dst_filename = dst.join(filename);
try_err!(fs::write(&dst_filename, bytes), &dst_filename);
}
if let Some(css) = css_file_extension {
let buffer = try_err!(fs::read_to_string(css), css);
let path = static_files::suffix_path("theme.css", resource_suffix);
let dst_path = dst.join(path);
try_err!(fs::write(&dst_path, buffer), &dst_path);
}
if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
static_files::for_each(|f: &static_files::StaticFile| {
let filename = static_dir.join(f.output_filename());
fs::write(&filename, f.minified()).map_err(|e| PathError::new(e, &filename))
})?;
}
Ok(())
}
fn write_search_desc(
cx: &mut Context<'_>,
krate: &Crate,
search_desc: &[(usize, String)],
) -> Result<(), Error> {
let crate_name = krate.name(cx.tcx()).to_string();
let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap();
let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]);
if path.exists() {
try_err!(fs::remove_dir_all(&path), &path);
}
for (i, (_, part)) in search_desc.iter().enumerate() {
let filename = static_files::suffix_path(
&format!("{crate_name}-desc-{i}-.js"),
&cx.shared.resource_suffix,
);
let path = path.join(filename);
let part = OrderedJson::serialize(&part).unwrap();
let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})");
create_parents(&path)?;
try_err!(fs::write(&path, part), &path);
}
Ok(())
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct CrateInfo {
version: CrateInfoVersion,
src_files_js: PartsAndLocations<SourcesPart>,
search_index_js: PartsAndLocations<SearchIndexPart>,
all_crates: PartsAndLocations<AllCratesPart>,
crates_index: PartsAndLocations<CratesIndexPart>,
trait_impl: PartsAndLocations<TraitAliasPart>,
type_impl: PartsAndLocations<TypeAliasPart>,
}
impl CrateInfo {
pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
parts_paths
.iter()
.map(|parts_path| {
let path = &parts_path.0;
let parts = try_err!(fs::read(&path), &path);
let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
Ok::<_, Error>(parts)
})
.collect::<Result<Vec<CrateInfo>, Error>>()
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
enum CrateInfoVersion {
V1,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(transparent)]
struct PartsAndLocations<P> {
parts: Vec<(PathBuf, P)>,
}
impl<P> Default for PartsAndLocations<P> {
fn default() -> Self {
Self { parts: Vec::default() }
}
}
impl<T, U> PartsAndLocations<Part<T, U>> {
fn push(&mut self, path: PathBuf, item: U) {
self.parts.push((path, Part { _artifact: PhantomData, item }));
}
fn with(path: PathBuf, part: U) -> Self {
let mut ret = Self::default();
ret.push(path, part);
ret
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(transparent)]
struct Part<T, U> {
#[serde(skip)]
_artifact: PhantomData<T>,
item: U,
}
impl<T, U: fmt::Display> fmt::Display for Part<T, U> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.item)
}
}
trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
type FileFormat: sorted_template::FileFormat;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct SearchIndex;
type SearchIndexPart = Part<SearchIndex, EscapedJson>;
impl CciPart for SearchIndexPart {
type FileFormat = sorted_template::Js;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
&crate_info.search_index_js
}
}
impl SearchIndexPart {
fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
SortedTemplate::from_before_after(
r"var searchIndex = new Map(JSON.parse('[",
r"]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);",
)
}
fn get(
search_index: OrderedJson,
resource_suffix: &str,
) -> Result<PartsAndLocations<Self>, Error> {
let path = suffix_path("search-index.js", resource_suffix);
let search_index = EscapedJson::from(search_index);
Ok(PartsAndLocations::with(path, search_index))
}
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct AllCrates;
type AllCratesPart = Part<AllCrates, OrderedJson>;
impl CciPart for AllCratesPart {
type FileFormat = sorted_template::Js;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
&crate_info.all_crates
}
}
impl AllCratesPart {
fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
SortedTemplate::from_before_after("window.ALL_CRATES = [", "];")
}
fn get(
crate_name_json: OrderedJson,
resource_suffix: &str,
) -> Result<PartsAndLocations<Self>, Error> {
let path = suffix_path("crates.js", resource_suffix);
Ok(PartsAndLocations::with(path, crate_name_json))
}
}
fn hack_get_external_crate_names(
doc_root: &Path,
resource_suffix: &str,
) -> Result<Vec<String>, Error> {
let path = doc_root.join(suffix_path("crates.js", resource_suffix));
let Ok(content) = fs::read_to_string(&path) else {
return Ok(Vec::default());
};
let regex = Regex::new(r"\[.*\]").unwrap();
let Some(content) = regex.find(&content) else {
return Err(Error::new("could not find crates list in crates.js", path));
};
let content: Vec<String> = try_err!(serde_json::from_str(content.as_str()), &path);
Ok(content)
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct CratesIndex;
type CratesIndexPart = Part<CratesIndex, String>;
impl CciPart for CratesIndexPart {
type FileFormat = sorted_template::Html;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
&crate_info.crates_index
}
}
impl CratesIndexPart {
fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
let page = layout::Page {
title: "Index of crates",
css_class: "mod sys",
root_path: "./",
static_root_path: cx.shared.static_root_path.as_deref(),
description: "List of crates",
resource_suffix: &cx.shared.resource_suffix,
rust_logo: true,
};
let layout = &cx.shared.layout;
let style_files = &cx.shared.style_files;
const DELIMITER: &str = "\u{FFFC}"; let content =
format!("<h1>List of all crates</h1><ul class=\"all-items\">{DELIMITER}</ul>");
let template = layout::render(layout, &page, "", content, &style_files);
match SortedTemplate::from_template(&template, DELIMITER) {
Ok(template) => template,
Err(e) => panic!(
"Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}"
),
}
}
fn get(crate_name: &str, external_crates: &[String]) -> Result<PartsAndLocations<Self>, Error> {
let mut ret = PartsAndLocations::default();
let path = PathBuf::from("index.html");
for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) {
let part = format!(
"<li><a href=\"{trailing_slash}index.html\">{crate_name}</a></li>",
trailing_slash = ensure_trailing_slash(crate_name),
);
ret.push(path.clone(), part);
}
Ok(ret)
}
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct Sources;
type SourcesPart = Part<Sources, EscapedJson>;
impl CciPart for SourcesPart {
type FileFormat = sorted_template::Js;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
&crate_info.src_files_js
}
}
impl SourcesPart {
fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
SortedTemplate::from_before_after(
r"var srcIndex = new Map(JSON.parse('[",
r"]'));
createSrcSidebar();",
)
}
fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result<PartsAndLocations<Self>, Error> {
let hierarchy = Rc::new(Hierarchy::default());
cx.shared
.local_sources
.iter()
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
.for_each(|source| hierarchy.add_path(source));
let path = suffix_path("src-files.js", &cx.shared.resource_suffix);
let hierarchy = hierarchy.to_json_string();
let part = OrderedJson::array_unsorted([crate_name, &hierarchy]);
let part = EscapedJson::from(part);
Ok(PartsAndLocations::with(path, part))
}
}
#[derive(Debug, Default)]
struct Hierarchy {
parent: Weak<Self>,
elem: OsString,
children: RefCell<FxIndexMap<OsString, Rc<Self>>>,
elems: RefCell<FxIndexSet<OsString>>,
}
impl Hierarchy {
fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
}
fn to_json_string(&self) -> OrderedJson {
let subs = self.children.borrow();
let files = self.elems.borrow();
let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion"))
.unwrap();
let mut out = Vec::from([name]);
if !subs.is_empty() || !files.is_empty() {
let subs = subs.iter().map(|(_, s)| s.to_json_string());
out.push(OrderedJson::array_sorted(subs));
}
if !files.is_empty() {
let files = files
.iter()
.map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap());
out.push(OrderedJson::array_sorted(files));
}
OrderedJson::array_unsorted(out)
}
fn add_path(self: &Rc<Self>, path: &Path) {
let mut h = Rc::clone(&self);
let mut elems = path
.components()
.filter_map(|s| match s {
Component::Normal(s) => Some(s.to_owned()),
Component::ParentDir => Some(OsString::from("..")),
_ => None,
})
.peekable();
loop {
let cur_elem = elems.next().expect("empty file path");
if cur_elem == ".." {
if let Some(parent) = h.parent.upgrade() {
h = parent;
}
continue;
}
if elems.peek().is_none() {
h.elems.borrow_mut().insert(cur_elem);
break;
} else {
let entry = Rc::clone(
h.children
.borrow_mut()
.entry(cur_elem.clone())
.or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))),
);
h = entry;
}
}
}
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct TypeAlias;
type TypeAliasPart = Part<TypeAlias, OrderedJson>;
impl CciPart for TypeAliasPart {
type FileFormat = sorted_template::Js;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
&crate_info.type_impl
}
}
impl TypeAliasPart {
fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
SortedTemplate::from_before_after(
r"(function() {
var type_impls = Object.fromEntries([",
r"]);
if (window.register_type_impls) {
window.register_type_impls(type_impls);
} else {
window.pending_type_impls = type_impls;
}
})()",
)
}
fn get(
cx: &mut Context<'_>,
krate: &Crate,
crate_name_json: &OrderedJson,
) -> Result<PartsAndLocations<Self>, Error> {
let cache = &Rc::clone(&cx.shared).cache;
let mut path_parts = PartsAndLocations::default();
let mut type_impl_collector = TypeImplCollector {
aliased_types: IndexMap::default(),
visited_aliases: FxHashSet::default(),
cache,
cx,
};
DocVisitor::visit_crate(&mut type_impl_collector, &krate);
let cx = type_impl_collector.cx;
let aliased_types = type_impl_collector.aliased_types;
for aliased_type in aliased_types.values() {
let impls = aliased_type
.impl_
.values()
.flat_map(|AliasedTypeImpl { impl_, type_aliases }| {
let mut ret = Vec::new();
let trait_ = impl_
.inner_impl()
.trait_
.as_ref()
.map(|trait_| format!("{:#}", trait_.print(cx)));
for &(type_alias_fqp, ref type_alias_item) in type_aliases {
let mut buf = Buffer::html();
cx.id_map = Default::default();
cx.deref_id_map = Default::default();
let target_did = impl_
.inner_impl()
.trait_
.as_ref()
.map(|trait_| trait_.def_id())
.or_else(|| impl_.inner_impl().for_.def_id(cache));
let provided_methods;
let assoc_link = if let Some(target_did) = target_did {
provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx());
AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods)
} else {
AssocItemLink::Anchor(None)
};
super::render_impl(
&mut buf,
cx,
*impl_,
&type_alias_item,
assoc_link,
RenderMode::Normal,
None,
&[],
ImplRenderingParameters {
show_def_docs: true,
show_default_items: true,
show_non_assoc_items: true,
toggle_open_by_default: true,
},
);
let text = buf.into_inner();
let type_alias_fqp = (*type_alias_fqp).iter().join("::");
if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) {
ret.last_mut()
.expect("already established that ret.last() is Some()")
.aliases
.push(type_alias_fqp);
} else {
ret.push(AliasSerializableImpl {
text,
trait_: trait_.clone(),
aliases: vec![type_alias_fqp],
})
}
}
ret
})
.collect::<Vec<_>>();
let mut path = PathBuf::from("type.impl");
for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
path.push(component.as_str());
}
let aliased_item_type = aliased_type.target_type;
path.push(&format!(
"{aliased_item_type}.{}.js",
aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
));
let part = OrderedJson::array_sorted(
impls.iter().map(OrderedJson::serialize).collect::<Result<Vec<_>, _>>().unwrap(),
);
path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
}
Ok(path_parts)
}
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct TraitAlias;
type TraitAliasPart = Part<TraitAlias, OrderedJson>;
impl CciPart for TraitAliasPart {
type FileFormat = sorted_template::Js;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
&crate_info.trait_impl
}
}
impl TraitAliasPart {
fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
SortedTemplate::from_before_after(
r"(function() {
var implementors = Object.fromEntries([",
r"]);
if (window.register_implementors) {
window.register_implementors(implementors);
} else {
window.pending_implementors = implementors;
}
})()",
)
}
fn get(
cx: &mut Context<'_>,
crate_name_json: &OrderedJson,
) -> Result<PartsAndLocations<Self>, Error> {
let cache = &cx.shared.cache;
let mut path_parts = PartsAndLocations::default();
for (&did, imps) in &cache.implementors {
let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
Some((_, t)) => (p, t),
None => continue,
},
None => match cache.external_paths.get(&did) {
Some((p, t)) => (p, t),
None => continue,
},
};
let implementors = imps
.iter()
.filter_map(|imp| {
if imp.impl_item.item_id.krate() == did.krate
|| !imp.impl_item.item_id.is_local()
{
None
} else {
Some(Implementor {
text: imp.inner_impl().print(false, cx).to_string(),
synthetic: imp.inner_impl().kind.is_auto(),
types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache),
})
}
})
.collect::<Vec<_>>();
if implementors.is_empty() && !cache.paths.contains_key(&did) {
continue;
}
let mut path = PathBuf::from("trait.impl");
for component in &remote_path[..remote_path.len() - 1] {
path.push(component.as_str());
}
path.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
let part = OrderedJson::array_sorted(
implementors
.iter()
.map(OrderedJson::serialize)
.collect::<Result<Vec<_>, _>>()
.unwrap(),
);
path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
}
Ok(path_parts)
}
}
struct Implementor {
text: String,
synthetic: bool,
types: Vec<String>,
}
impl Serialize for Implementor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(&self.text)?;
if self.synthetic {
seq.serialize_element(&1)?;
seq.serialize_element(&self.types)?;
}
seq.end()
}
}
struct TypeImplCollector<'cx, 'cache, 'item> {
aliased_types: IndexMap<DefId, AliasedType<'cache, 'item>>,
visited_aliases: FxHashSet<DefId>,
cache: &'cache Cache,
cx: &'cache mut Context<'cx>,
}
struct AliasedType<'cache, 'item> {
target_fqp: &'cache [Symbol],
target_type: ItemType,
impl_: IndexMap<ItemId, AliasedTypeImpl<'cache, 'item>>,
}
struct AliasedTypeImpl<'cache, 'item> {
impl_: &'cache Impl,
type_aliases: Vec<(&'cache [Symbol], &'item Item)>,
}
impl<'cx, 'cache, 'item> DocVisitor<'item> for TypeImplCollector<'cx, 'cache, 'item> {
fn visit_item(&mut self, it: &'item Item) {
self.visit_item_recur(it);
let cache = self.cache;
let ItemKind::TypeAliasItem(ref t) = it.kind else { return };
let Some(self_did) = it.item_id.as_def_id() else { return };
if !self.visited_aliases.insert(self_did) {
return;
}
let Some(target_did) = t.type_.def_id(cache) else { return };
let get_extern = { || cache.external_paths.get(&target_did) };
let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern)
else {
return;
};
let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
let impl_ = cache
.impls
.get(&target_did)
.map(|v| &v[..])
.unwrap_or_default()
.iter()
.map(|impl_| {
(impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() })
})
.collect();
AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
});
let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) };
let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
return;
};
let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
let mut seen_impls: FxHashSet<ItemId> = cache
.impls
.get(&self_did)
.map(|s| &s[..])
.unwrap_or_default()
.iter()
.map(|i| i.impl_item.item_id)
.collect();
for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
let Some(impl_did) = impl_item_id.as_def_id() else { continue };
let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx());
if !reject_cx.types_may_unify(aliased_ty, for_ty) {
continue;
}
if !seen_impls.insert(*impl_item_id) {
continue;
}
aliased_type_impl.type_aliases.push((&self_fqp[..], it));
}
}
}
struct AliasSerializableImpl {
text: String,
trait_: Option<String>,
aliases: Vec<String>,
}
impl Serialize for AliasSerializableImpl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(&self.text)?;
if let Some(trait_) = &self.trait_ {
seq.serialize_element(trait_)?;
} else {
seq.serialize_element(&0)?;
}
for type_ in &self.aliases {
seq.serialize_element(type_)?;
}
seq.end()
}
}
fn get_path_parts<T: CciPart>(
dst: &Path,
crates_info: &[CrateInfo],
) -> FxIndexMap<PathBuf, Vec<String>> {
let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default();
crates_info
.iter()
.map(|crate_info| T::from_crate_info(crate_info).parts.iter())
.flatten()
.for_each(|(path, part)| {
let path = dst.join(&path);
let part = part.to_string();
templates.entry(path).or_default().push(part);
});
templates
}
fn create_parents(path: &Path) -> Result<(), Error> {
let parent = path.parent().expect("should not have an empty path here");
try_err!(fs::create_dir_all(parent), parent);
Ok(())
}
fn read_template_or_blank<F, T: FileFormat>(
mut make_blank: F,
path: &Path,
should_merge: &ShouldMerge,
) -> Result<SortedTemplate<T>, Error>
where
F: FnMut() -> SortedTemplate<T>,
{
if !should_merge.read_rendered_cci {
return Ok(make_blank());
}
match fs::read_to_string(&path) {
Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
Err(e) => Err(Error::new(e, &path)),
}
}
fn write_rendered_cci<T: CciPart, F>(
mut make_blank: F,
dst: &Path,
crates_info: &[CrateInfo],
should_merge: &ShouldMerge,
) -> Result<(), Error>
where
F: FnMut() -> SortedTemplate<T::FileFormat>,
{
for (path, parts) in get_path_parts::<T>(dst, crates_info) {
create_parents(&path)?;
let mut template =
read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
for part in parts {
template.append(part);
}
let mut file = try_err!(File::create_buffered(&path), &path);
try_err!(write!(file, "{template}"), &path);
try_err!(file.flush(), &path);
}
Ok(())
}
#[cfg(test)]
mod tests;