diff options
Diffstat (limited to 'src/librustdoc/html/render')
| -rw-r--r-- | src/librustdoc/html/render/context.rs | 53 | ||||
| -rw-r--r-- | src/librustdoc/html/render/mod.rs | 25 | ||||
| -rw-r--r-- | src/librustdoc/html/render/print_item.rs | 106 | ||||
| -rw-r--r-- | src/librustdoc/html/render/sidebar.rs | 70 | ||||
| -rw-r--r-- | src/librustdoc/html/render/write_shared.rs | 271 |
5 files changed, 424 insertions, 101 deletions
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index bf8d1a80337..6da9e45a1da 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -7,10 +7,8 @@ use std::sync::mpsc::{channel, Receiver}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE}; -use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use rustc_span::def_id::DefId; use rustc_span::edition::Edition; use rustc_span::source_map::FileName; use rustc_span::{sym, Symbol}; @@ -25,13 +23,13 @@ use super::{ AllTypes, LinkFromSrc, StylePath, }; use crate::clean::utils::has_doc_flag; -use crate::clean::{self, types::ExternalLocation, ExternalCrate, TypeAliasItem}; +use crate::clean::{self, types::ExternalLocation, ExternalCrate}; use crate::config::{ModuleSorting, RenderOptions}; use crate::docfs::{DocFS, PathError}; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; -use crate::formats::{self, FormatRenderer}; +use crate::formats::FormatRenderer; use crate::html::escape::Escape; use crate::html::format::{join_with_double_colon, Buffer}; use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; @@ -150,53 +148,6 @@ impl SharedContext<'_> { pub(crate) fn edition(&self) -> Edition { self.tcx.sess.edition() } - - /// Returns a list of impls on the given type, and, if it's a type alias, - /// other types that it aliases. - pub(crate) fn all_impls_for_item<'a>( - &'a self, - it: &clean::Item, - did: DefId, - ) -> Vec<&'a formats::Impl> { - let tcx = self.tcx; - let cache = &self.cache; - let mut saw_impls = FxHashSet::default(); - let mut v: Vec<&formats::Impl> = cache - .impls - .get(&did) - .map(Vec::as_slice) - .unwrap_or(&[]) - .iter() - .filter(|i| saw_impls.insert(i.def_id())) - .collect(); - if let TypeAliasItem(ait) = &*it.kind && - let aliased_clean_type = ait.item_type.as_ref().unwrap_or(&ait.type_) && - let Some(aliased_type_defid) = aliased_clean_type.def_id(cache) && - let Some(av) = cache.impls.get(&aliased_type_defid) && - let Some(alias_def_id) = it.item_id.as_def_id() - { - // This branch of the compiler compares types structually, but does - // not check trait bounds. That's probably fine, since type aliases - // don't normally constrain on them anyway. - // https://github.com/rust-lang/rust/issues/21903 - // - // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this to use type unification. - // Be aware of `tests/rustdoc/issue-112515-impl-ty-alias.rs` which might regress. - let aliased_ty = tcx.type_of(alias_def_id).skip_binder(); - let reject_cx = DeepRejectCtxt { - treat_obligation_params: TreatParams::AsCandidateKey, - }; - v.extend(av.iter().filter(|impl_| { - if let Some(impl_def_id) = impl_.impl_item.item_id.as_def_id() { - reject_cx.types_may_unify(aliased_ty, tcx.type_of(impl_def_id).skip_binder()) - && saw_impls.insert(impl_def_id) - } else { - false - } - })); - } - v - } } impl<'tcx> Context<'tcx> { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e01341acf43..d9086433608 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1145,13 +1145,13 @@ pub(crate) fn render_all_impls( fn render_assoc_items<'a, 'cx: 'a>( cx: &'a mut Context<'cx>, containing_item: &'a clean::Item, - did: DefId, + it: DefId, what: AssocItemRender<'a>, ) -> impl fmt::Display + 'a + Captures<'cx> { let mut derefs = DefIdSet::default(); - derefs.insert(did); + derefs.insert(it); display_fn(move |f| { - render_assoc_items_inner(f, cx, containing_item, did, what, &mut derefs); + render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs); Ok(()) }) } @@ -1160,17 +1160,15 @@ fn render_assoc_items_inner( mut w: &mut dyn fmt::Write, cx: &mut Context<'_>, containing_item: &clean::Item, - did: DefId, + it: DefId, what: AssocItemRender<'_>, derefs: &mut DefIdSet, ) { info!("Documenting associated items of {:?}", containing_item.name); let shared = Rc::clone(&cx.shared); - let v = shared.all_impls_for_item(containing_item, did); - let v = v.as_slice(); - let (non_trait, traits): (Vec<&Impl>, _) = - v.iter().partition(|i| i.inner_impl().trait_.is_none()); - let mut saw_impls = FxHashSet::default(); + let cache = &shared.cache; + let Some(v) = cache.impls.get(&it) else { return }; + let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none()); if !non_trait.is_empty() { let mut tmp_buf = Buffer::html(); let (render_mode, id, class_html) = match what { @@ -1199,9 +1197,6 @@ fn render_assoc_items_inner( }; let mut impls_buf = Buffer::html(); for i in &non_trait { - if !saw_impls.insert(i.def_id()) { - continue; - } render_impl( &mut impls_buf, cx, @@ -1247,10 +1242,8 @@ fn render_assoc_items_inner( let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = traits.into_iter().partition(|t| t.inner_impl().kind.is_auto()); - let (blanket_impl, concrete): (Vec<&Impl>, _) = concrete - .into_iter() - .filter(|t| saw_impls.insert(t.def_id())) - .partition(|t| t.inner_impl().kind.is_blanket()); + let (blanket_impl, concrete): (Vec<&Impl>, _) = + concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket()); render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl); } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f6432dc61ae..fdf45569061 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1066,6 +1066,8 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } } + // [RUSTDOCIMPL] trait.impl + // // Include implementors in crates that depend on the current crate. // // This is complicated by the way rustdoc is invoked, which is basically @@ -1101,7 +1103,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: // ``` // // Basically, we want `C::Baz` and `A::Foo` to show the same set of - // impls, which is easier if they both treat `/implementors/A/trait.Foo.js` + // impls, which is easier if they both treat `/trait.impl/A/trait.Foo.js` // as the Single Source of Truth. // // We also want the `impl Baz for Quux` to be written to @@ -1110,7 +1112,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: // because that'll load faster, and it's better for SEO. And we don't want // the same impl to show up twice on the same page. // - // To make this work, the implementors JS file has a structure kinda + // To make this work, the trait.impl/A/trait.Foo.js JS file has a structure kinda // like this: // // ```js @@ -1127,7 +1129,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: // So C's HTML will have something like this: // // ```html - // <script src="/implementors/A/trait.Foo.js" + // <script src="/trait.impl/A/trait.Foo.js" // data-ignore-extern-crates="A,B" async></script> // ``` // @@ -1137,7 +1139,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: // [JSONP]: https://en.wikipedia.org/wiki/JSONP let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..") .take(cx.current.len()) - .chain(std::iter::once("implementors")) + .chain(std::iter::once("trait.impl")) .collect(); if let Some(did) = it.item_id.as_def_id() && let get_extern = { || cache.external_paths.get(&did).map(|s| &s.0) } && @@ -1319,6 +1321,102 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c // we need #14072 to make sense of the generics. write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); write!(w, "{}", document_type_layout(cx, def_id)); + + // [RUSTDOCIMPL] type.impl + // + // Include type definitions from the alias target type. + // + // Earlier versions of this code worked by having `render_assoc_items` + // include this data directly. That generates *O*`(types*impls)` of HTML + // text, and some real crates have a lot of types and impls. + // + // To create the same UX without generating half a gigabyte of HTML for a + // crate that only contains 20 megabytes of actual documentation[^115718], + // rustdoc stashes these type-alias-inlined docs in a [JSONP] + // "database-lite". The file itself is generated in `write_shared.rs`, + // and hooks into functions provided by `main.js`. + // + // The format of `trait.impl` and `type.impl` JS files are superficially + // similar. Each line, except the JSONP wrapper itself, belongs to a crate, + // and they are otherwise separate (rustdoc should be idempotent). The + // "meat" of the file is HTML strings, so the frontend code is very simple. + // Links are relative to the doc root, though, so the frontend needs to fix + // that up, and inlined docs can reuse these files. + // + // However, there are a few differences, caused by the sophisticated + // features that type aliases have. Consider this crate graph: + // + // ```text + // --------------------------------- + // | crate A: struct Foo<T> | + // | type Bar = Foo<i32> | + // | impl X for Foo<i8> | + // | impl Y for Foo<i32> | + // --------------------------------- + // | + // ---------------------------------- + // | crate B: type Baz = A::Foo<i8> | + // | type Xyy = A::Foo<i8> | + // | impl Z for Xyy | + // ---------------------------------- + // ``` + // + // The type.impl/A/struct.Foo.js JS file has a structure kinda like this: + // + // ```js + // JSONP({ + // "A": [["impl Y for Foo<i32>", "Y", "A::Bar"]], + // "B": [["impl X for Foo<i8>", "X", "B::Baz", "B::Xyy"], ["impl Z for Xyy", "Z", "B::Baz"]], + // }); + // ``` + // + // When the type.impl file is loaded, only the current crate's docs are + // actually used. The main reason to bundle them together is that there's + // enough duplication in them for DEFLATE to remove the redundancy. + // + // The contents of a crate are a list of impl blocks, themselves + // represented as lists. The first item in the sublist is the HTML block, + // the second item is the name of the trait (which goes in the sidebar), + // and all others are the names of type aliases that successfully match. + // + // This way: + // + // - There's no need to generate these files for types that have no aliases + // in the current crate. If a dependent crate makes a type alias, it'll + // take care of generating its own docs. + // - There's no need to reimplement parts of the type checker in + // JavaScript. The Rust backend does the checking, and includes its + // results in the file. + // - Docs defined directly on the type alias are dropped directly in the + // HTML by `render_assoc_items`, and are accessible without JavaScript. + // The JSONP file will not list impl items that are known to be part + // of the main HTML file already. + // + // [JSONP]: https://en.wikipedia.org/wiki/JSONP + // [^115718]: https://github.com/rust-lang/rust/issues/115718 + let cloned_shared = Rc::clone(&cx.shared); + let cache = &cloned_shared.cache; + if let Some(target_did) = t.type_.def_id(cache) && + 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) && + target_type.is_adt() && // primitives cannot be inlined + let Some(self_did) = it.item_id.as_def_id() && + 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) + { + let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..") + .take(cx.current.len()) + .chain(std::iter::once("type.impl")) + .collect(); + js_src_path.extend(target_fqp[..target_fqp.len() - 1].iter().copied()); + js_src_path.push_fmt(format_args!("{target_type}.{}.js", target_fqp.last().unwrap())); + let self_path = self_fqp.iter().map(Symbol::as_str).collect::<Vec<&str>>().join("::"); + write!( + w, + "<script src=\"{src}\" data-self-path=\"{self_path}\" async></script>", + src = js_src_path.finish(), + ); + } } fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) { diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index fb429f237e3..4e8d88c55b6 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -38,18 +38,19 @@ pub(crate) struct LinkBlock<'a> { /// as well as the link to it, e.g. `#implementations`. /// Will be rendered inside an `<h3>` tag heading: Link<'a>, + class: &'static str, links: Vec<Link<'a>>, /// Render the heading even if there are no links force_render: bool, } impl<'a> LinkBlock<'a> { - pub fn new(heading: Link<'a>, links: Vec<Link<'a>>) -> Self { - Self { heading, links, force_render: false } + pub fn new(heading: Link<'a>, class: &'static str, links: Vec<Link<'a>>) -> Self { + Self { heading, links, class, force_render: false } } - pub fn forced(heading: Link<'a>) -> Self { - Self { heading, links: vec![], force_render: true } + pub fn forced(heading: Link<'a>, class: &'static str) -> Self { + Self { heading, links: vec![], class, force_render: true } } pub fn should_render(&self) -> bool { @@ -157,7 +158,7 @@ fn sidebar_struct<'a>( }; let mut items = vec![]; if let Some(name) = field_name { - items.push(LinkBlock::new(Link::new("fields", name), fields)); + items.push(LinkBlock::new(Link::new("fields", name), "structfield", fields)); } sidebar_assoc_items(cx, it, &mut items); items @@ -214,12 +215,15 @@ fn sidebar_trait<'a>( ("foreign-impls", "Implementations on Foreign Types", foreign_impls), ] .into_iter() - .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), items)) + .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items)) .collect(); sidebar_assoc_items(cx, it, &mut blocks); - blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"))); + blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"), "impl")); if t.is_auto(cx.tcx()) { - blocks.push(LinkBlock::forced(Link::new("synthetic-implementors", "Auto Implementors"))); + blocks.push(LinkBlock::forced( + Link::new("synthetic-implementors", "Auto Implementors"), + "impl-auto", + )); } blocks } @@ -245,7 +249,7 @@ fn sidebar_type_alias<'a>( ) -> Vec<LinkBlock<'a>> { let mut items = vec![]; if let Some(inner_type) = &t.inner_type { - items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type"))); + items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type"), "type")); match inner_type { clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive: _ } => { let mut variants = variants @@ -256,12 +260,12 @@ fn sidebar_type_alias<'a>( .collect::<Vec<_>>(); variants.sort_unstable(); - items.push(LinkBlock::new(Link::new("variants", "Variants"), variants)); + items.push(LinkBlock::new(Link::new("variants", "Variants"), "variant", variants)); } clean::TypeAliasInnerType::Union { fields } | clean::TypeAliasInnerType::Struct { ctor_kind: _, fields } => { let fields = get_struct_fields_name(fields); - items.push(LinkBlock::new(Link::new("fields", "Fields"), fields)); + items.push(LinkBlock::new(Link::new("fields", "Fields"), "field", fields)); } } } @@ -275,7 +279,7 @@ fn sidebar_union<'a>( u: &'a clean::Union, ) -> Vec<LinkBlock<'a>> { let fields = get_struct_fields_name(&u.fields); - let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)]; + let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields)]; sidebar_assoc_items(cx, it, &mut items); items } @@ -287,12 +291,11 @@ fn sidebar_assoc_items<'a>( links: &mut Vec<LinkBlock<'a>>, ) { let did = it.item_id.expect_def_id(); - let v = cx.shared.all_impls_for_item(it, it.item_id.expect_def_id()); - let v = v.as_slice(); + let cache = cx.cache(); let mut assoc_consts = Vec::new(); let mut methods = Vec::new(); - if !v.is_empty() { + if let Some(v) = cache.impls.get(&did) { let mut used_links = FxHashSet::default(); let mut id_map = IdMap::new(); @@ -328,7 +331,7 @@ fn sidebar_assoc_items<'a>( cx, &mut deref_methods, impl_, - v.iter().copied(), + v, &mut derefs, &mut used_links, ); @@ -341,12 +344,16 @@ fn sidebar_assoc_items<'a>( sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl) } else { - std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![])) + std::array::from_fn(|_| LinkBlock::new(Link::empty(), "", vec![])) }; let mut blocks = vec![ - LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts), - LinkBlock::new(Link::new("implementations", "Methods"), methods), + LinkBlock::new( + Link::new("implementations", "Associated Constants"), + "associatedconstant", + assoc_consts, + ), + LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), ]; blocks.append(&mut deref_methods); blocks.extend([concrete, synthetic, blanket]); @@ -358,7 +365,7 @@ fn sidebar_deref_methods<'a>( cx: &'a Context<'_>, out: &mut Vec<LinkBlock<'a>>, impl_: &Impl, - v: impl Iterator<Item = &'a Impl>, + v: &[Impl], derefs: &mut DefIdSet, used_links: &mut FxHashSet<String>, ) { @@ -383,7 +390,7 @@ fn sidebar_deref_methods<'a>( // Avoid infinite cycles return; } - let deref_mut = { v }.any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); let inner_impl = target .def_id(c) .or_else(|| { @@ -415,7 +422,7 @@ fn sidebar_deref_methods<'a>( ); // We want links' order to be reproducible so we don't use unstable sort. ret.sort(); - out.push(LinkBlock::new(Link::new(id, title), ret)); + out.push(LinkBlock::new(Link::new(id, title), "deref-methods", ret)); } } @@ -434,7 +441,7 @@ fn sidebar_deref_methods<'a>( cx, out, target_deref_impl, - target_impls.iter(), + target_impls, derefs, used_links, ); @@ -454,7 +461,7 @@ fn sidebar_enum<'a>( .collect::<Vec<_>>(); variants.sort_unstable(); - let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)]; + let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), "variant", variants)]; sidebar_assoc_items(cx, it, &mut items); items } @@ -468,7 +475,7 @@ pub(crate) fn sidebar_module_like( .filter(|sec| item_sections_in_use.contains(sec)) .map(|sec| Link::new(sec.id(), sec.name())) .collect(); - LinkBlock::new(Link::empty(), item_sections) + LinkBlock::new(Link::empty(), "", item_sections) } fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> { @@ -529,12 +536,21 @@ fn sidebar_render_assoc_items( let synthetic = format_impls(synthetic, id_map); let blanket = format_impls(blanket_impl, id_map); [ - LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete), + LinkBlock::new( + Link::new("trait-implementations", "Trait Implementations"), + "trait-implementation", + concrete, + ), LinkBlock::new( Link::new("synthetic-implementations", "Auto Trait Implementations"), + "synthetic-implementation", synthetic, ), - LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket), + LinkBlock::new( + Link::new("blanket-implementations", "Blanket Implementations"), + "blanket-implementation", + blanket, + ), ] } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index e68d5ab2fbd..3e58dd96ed9 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -5,18 +5,28 @@ use std::io::{self, BufReader}; use std::path::{Component, Path}; use std::rc::{Rc, Weak}; +use indexmap::IndexMap; use itertools::Itertools; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; +use rustc_span::def_id::DefId; +use rustc_span::Symbol; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use super::{collect_paths_for_type, ensure_trailing_slash, Context}; -use crate::clean::Crate; +use crate::clean::{Crate, Item, ItemId, ItemKind}; use crate::config::{EmitType, RenderOptions}; use crate::docfs::PathError; use crate::error::Error; +use crate::formats::cache::Cache; +use crate::formats::item_type::ItemType; +use crate::formats::{Impl, RenderMode}; +use crate::html::format::Buffer; +use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::{layout, static_files}; +use crate::visit::DocVisitor; use crate::{try_err, try_none}; /// Rustdoc writes out two kinds of shared files: @@ -361,9 +371,264 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; } } + let cloned_shared = Rc::clone(&cx.shared); + let cache = &cloned_shared.cache; + + // Collect the list of aliased types and their aliases. + // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+type.impl&type=code> + // + // The clean AST has type aliases that point at their types, but + // this visitor works to reverse that: `aliased_types` is a map + // from target to the aliases that reference it, and each one + // will generate one file. + struct TypeImplCollector<'cx, 'cache> { + // Map from DefId-of-aliased-type to its data. + aliased_types: IndexMap<DefId, AliasedType<'cache>>, + visited_aliases: FxHashSet<DefId>, + cache: &'cache Cache, + cx: &'cache mut Context<'cx>, + } + // Data for an aliased type. + // + // In the final file, the format will be roughly: + // + // ```json + // // type.impl/CRATE/TYPENAME.js + // JSONP( + // "CRATE": [ + // ["IMPL1 HTML", "ALIAS1", "ALIAS2", ...], + // ["IMPL2 HTML", "ALIAS3", "ALIAS4", ...], + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ struct AliasedType + // ... + // ] + // ) + // ``` + struct AliasedType<'cache> { + // This is used to generate the actual filename of this aliased type. + target_fqp: &'cache [Symbol], + target_type: ItemType, + // This is the data stored inside the file. + // ItemId is used to deduplicate impls. + impl_: IndexMap<ItemId, AliasedTypeImpl<'cache>>, + } + // The `impl_` contains data that's used to figure out if an alias will work, + // and to generate the HTML at the end. + // + // The `type_aliases` list is built up with each type alias that matches. + struct AliasedTypeImpl<'cache> { + impl_: &'cache Impl, + type_aliases: Vec<(&'cache [Symbol], Item)>, + } + impl<'cx, 'cache> DocVisitor for TypeImplCollector<'cx, 'cache> { + fn visit_item(&mut self, it: &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(); + // Exclude impls that are directly on this type. They're already in the HTML. + // Some inlining scenarios can cause there to be two versions of the same + // impl: one on the type alias and one on the underlying target type. + 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_ { + // Only include this impl if it actually unifies with this alias. + // Synthetic impls are not included; those are also included in the HTML. + // + // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this + // to use type unification. + // Be aware of `tests/rustdoc/type-alias/deeply-nested-112515.rs` which might regress. + 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 { treat_obligation_params: TreatParams::AsCandidateKey }; + if !reject_cx.types_may_unify(aliased_ty, for_ty) { + continue; + } + // Avoid duplicates + if !seen_impls.insert(*impl_item_id) { + continue; + } + // This impl was not found in the set of rejected impls + aliased_type_impl.type_aliases.push((&self_fqp[..], it.clone())); + } + } + } + let mut type_impl_collector = TypeImplCollector { + aliased_types: IndexMap::default(), + visited_aliases: FxHashSet::default(), + cache, + cx, + }; + DocVisitor::visit_crate(&mut type_impl_collector, &krate); + // Final serialized form of the alias impl + 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() + } + } + let cx = type_impl_collector.cx; + let dst = cx.dst.join("type.impl"); + 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))); + // render_impl will filter out "impossible-to-call" methods + // to make that functionality work here, it needs to be called with + // each type alias, and if it gives a different result, split the impl + 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 impls = format!( + r#""{}":{}"#, + krate.name(cx.tcx()), + serde_json::to_string(&impls).expect("failed serde conversion"), + ); + + let mut mydst = dst.clone(); + for part in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] { + mydst.push(part.to_string()); + } + cx.shared.ensure_dir(&mydst)?; + let aliased_item_type = aliased_type.target_type; + mydst.push(&format!( + "{aliased_item_type}.{}.js", + aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] + )); + + let (mut all_impls, _) = try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst); + all_impls.push(impls); + // Sort the implementors by crate so the file will be generated + // identically even with rustdoc running in parallel. + all_impls.sort(); + + let mut v = String::from("(function() {var type_impls = {\n"); + v.push_str(&all_impls.join(",\n")); + v.push_str("\n};"); + v.push_str( + "if (window.register_type_impls) {\ + window.register_type_impls(type_impls);\ + } else {\ + window.pending_type_impls = type_impls;\ + }", + ); + v.push_str("})()"); + cx.shared.fs.write(mydst, v)?; + } + // Update the list of all implementors for traits - let dst = cx.dst.join("implementors"); - let cache = cx.cache(); + // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+trait.impl&type=code> + let dst = cx.dst.join("trait.impl"); for (&did, imps) in &cache.implementors { // Private modules can leak through to this phase of rustdoc, which // could contain implementations for otherwise private types. In some |
