diff --git a/Cargo.lock b/Cargo.lock index 7aa243ad8b5..43178aeaaaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4688,6 +4688,7 @@ dependencies = [ "arrayvec", "askama", "expect-test", + "indexmap 2.0.0", "itertools", "minifier", "once_cell", diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 38935b7d1bb..0e01c100f9c 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -10,6 +10,7 @@ path = "lib.rs" arrayvec = { version = "0.7", default-features = false } askama = { version = "0.12", default-features = false, features = ["config"] } itertools = "0.10.1" +indexmap = "2" minifier = "0.2.3" once_cell = "1.10.0" regex = "1" diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index eb18ecf662c..ee55928ef07 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -26,6 +26,8 @@ use crate::clean::{ use crate::core::DocContext; use crate::formats::item_type::ItemType; +use super::Item; + /// Attempt to inline a definition into this AST. /// /// This function will fetch the definition specified, and if it is @@ -83,7 +85,7 @@ pub(crate) fn try_inline( Res::Def(DefKind::TyAlias, did) => { record_extern_fqn(cx, did, ItemType::TypeAlias); build_impls(cx, did, attrs_without_docs, &mut ret); - clean::TypeAliasItem(build_type_alias(cx, did)) + clean::TypeAliasItem(build_type_alias(cx, did, &mut ret)) } Res::Def(DefKind::Enum, did) => { record_extern_fqn(cx, did, ItemType::Enum); @@ -281,11 +283,15 @@ fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union { clean::Union { generics, fields } } -fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box { +fn build_type_alias( + cx: &mut DocContext<'_>, + did: DefId, + ret: &mut Vec, +) -> Box { let predicates = cx.tcx.explicit_predicates_of(did); let ty = cx.tcx.type_of(did).instantiate_identity(); let type_ = clean_middle_ty(ty::Binder::dummy(ty), cx, Some(did), None); - let inner_type = clean_ty_alias_inner_type(ty, cx); + let inner_type = clean_ty_alias_inner_type(ty, cx, ret); Box::new(clean::TypeAlias { type_, diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 7becc156142..c89db164000 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -934,11 +934,16 @@ fn clean_ty_generics<'tcx>( fn clean_ty_alias_inner_type<'tcx>( ty: Ty<'tcx>, cx: &mut DocContext<'tcx>, + ret: &mut Vec, ) -> Option { let ty::Adt(adt_def, args) = ty.kind() else { return None; }; + if !adt_def.did().is_local() { + inline::build_impls(cx, adt_def.did(), None, ret); + } + Some(if adt_def.is_enum() { let variants: rustc_index::IndexVec<_, _> = adt_def .variants() @@ -946,6 +951,10 @@ fn clean_ty_alias_inner_type<'tcx>( .map(|variant| clean_variant_def_with_args(variant, args, cx)) .collect(); + if !adt_def.did().is_local() { + inline::record_extern_fqn(cx, adt_def.did(), ItemType::Enum); + } + TypeAliasInnerType::Enum { variants, is_non_exhaustive: adt_def.is_variant_list_non_exhaustive(), @@ -961,8 +970,14 @@ fn clean_ty_alias_inner_type<'tcx>( clean_variant_def_with_args(variant, args, cx).kind.inner_items().cloned().collect(); if adt_def.is_struct() { + if !adt_def.did().is_local() { + inline::record_extern_fqn(cx, adt_def.did(), ItemType::Struct); + } TypeAliasInnerType::Struct { ctor_kind: variant.ctor_kind(), fields } } else { + if !adt_def.did().is_local() { + inline::record_extern_fqn(cx, adt_def.did(), ItemType::Union); + } TypeAliasInnerType::Union { fields } } }) @@ -2744,14 +2759,24 @@ fn clean_maybe_renamed_item<'tcx>( } let ty = cx.tcx.type_of(def_id).instantiate_identity(); - let inner_type = clean_ty_alias_inner_type(ty, cx); - TypeAliasItem(Box::new(TypeAlias { - generics, - inner_type, - type_: rustdoc_ty, - item_type: Some(type_), - })) + let mut ret = Vec::new(); + let inner_type = clean_ty_alias_inner_type(ty, cx, &mut ret); + + ret.push(generate_item_with_correct_attrs( + cx, + TypeAliasItem(Box::new(TypeAlias { + generics, + inner_type, + type_: rustdoc_ty, + item_type: Some(type_), + })), + item.owner_id.def_id.to_def_id(), + name, + import_id, + renamed, + )); + return ret; } ItemKind::Enum(ref def, generics) => EnumItem(Enum { variants: def.variants.iter().map(|v| clean_variant(v, cx)).collect(), diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 4ccb5f2be34..abff77253ea 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -50,8 +50,8 @@ pub(crate) struct Cache { /// Unlike 'paths', this mapping ignores any renames that occur /// due to 'use' statements. /// - /// This map is used when writing out the special 'implementors' - /// javascript file. By using the exact path that the type + /// This map is used when writing out the `impl.trait` and `impl.type` + /// javascript files. By using the exact path that the type /// is declared with, we ensure that each path will be identical /// to the path used if the corresponding type is inlined. By /// doing this, we can detect duplicate impls on a trait page, and only display diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index e37d16f5bd0..def3a90c8e8 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -180,6 +180,9 @@ impl ItemType { pub(crate) fn is_method(&self) -> bool { matches!(*self, ItemType::Method | ItemType::TyMethod) } + pub(crate) fn is_adt(&self) -> bool { + matches!(*self, ItemType::Struct | ItemType::Union | ItemType::Enum) + } } impl fmt::Display for ItemType { diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index b553954845b..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 @@ -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 | + // | type Bar = Foo | + // | impl X for Foo | + // | impl Y for Foo | + // --------------------------------- + // | + // ---------------------------------- + // | crate B: type Baz = A::Foo | + // | type Xyy = A::Foo | + // | 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", "Y", "A::Bar"]], + // "B": [["impl X for Foo", "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::>().join("::"); + write!( + w, + "", + 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 9f7744b45d3..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 `

` tag heading: Link<'a>, + class: &'static str, links: Vec>, /// Render the heading even if there are no links force_render: bool, } impl<'a> LinkBlock<'a> { - pub fn new(heading: Link<'a>, links: Vec>) -> Self { - Self { heading, links, force_render: false } + pub fn new(heading: Link<'a>, class: &'static str, links: Vec>) -> 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> { 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::>(); 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> { 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 } @@ -340,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]); @@ -414,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)); } } @@ -453,7 +461,7 @@ fn sidebar_enum<'a>( .collect::>(); 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 } @@ -467,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> { @@ -528,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 b162ea99d8f..4054281f346 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,247 @@ 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. + // + // + // 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>, + visited_aliases: FxHashSet, + 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>, + } + // 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 = 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, + aliases: Vec, + } + impl Serialize for AliasSerializableImpl { + fn serialize(&self, serializer: S) -> Result + 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(|path| path.last().to_string()); + // 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(); + super::render_impl( + &mut buf, + cx, + *impl_, + &type_alias_item, + AssocItemLink::Anchor(None), + 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::>(); + 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("trait.impl"); - let cache = cx.cache(); 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 diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 2e9897ef82b..6cd57c8c598 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -549,6 +549,7 @@ function preLoadCss(cssUrl) { } } + // window.register_implementors = imp => { const implementors = document.getElementById("implementors-list"); const synthetic_implementors = document.getElementById("synthetic-implementors-list"); @@ -615,7 +616,7 @@ function preLoadCss(cssUrl) { onEachLazy(code.getElementsByTagName("a"), elem => { const href = elem.getAttribute("href"); - if (href && !/^(?:[a-z+]+:)?\/\//.test(href)) { + if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) { elem.setAttribute("href", window.rootPath + href); } }); @@ -639,6 +640,177 @@ function preLoadCss(cssUrl) { window.register_implementors(window.pending_implementors); } + // + window.register_type_impls = imp => { + if (!imp || !imp[window.currentCrate]) { + return; + } + window.pending_type_impls = null; + const idMap = new Map(); + + let implementations = document.getElementById("implementations-list"); + let trait_implementations = document.getElementById("trait-implementations-list"); + + // We want to include the current type alias's impls, and no others. + const script = document.querySelector("script[data-self-path]"); + const selfPath = script ? script.getAttribute("data-self-path") : null; + + // These sidebar blocks need filled in, too. + const sidebarSection = document.querySelector(".sidebar section"); + let methods = document.querySelector(".sidebar .block.method"); + let associatedTypes = document.querySelector(".sidebar .block.associatedtype"); + let associatedConstants = document.querySelector(".sidebar .block.associatedconstant"); + let sidebarTraitList = document.querySelector(".sidebar .block.trait-implementation"); + + for (const impList of imp[window.currentCrate]) { + const types = impList.slice(2); + const text = impList[0]; + const isTrait = impList[1] !== 0; + const traitName = impList[1]; + if (types.indexOf(selfPath) === -1) { + continue; + } + let outputList = isTrait ? trait_implementations : implementations; + if (outputList === null) { + const outputListName = isTrait ? "Trait Implementations" : "Implementations"; + const outputListId = isTrait ? + "trait-implementations-list" : + "implementations-list"; + const outputListHeaderId = isTrait ? "trait-implementations" : "implementations"; + const outputListH = document.createElement("h2"); + outputListH.id = outputListHeaderId; + outputListH.innerText = outputListName; + outputList = document.createElement("div"); + outputList.id = outputListId; + if (isTrait) { + const link = document.createElement("a"); + link.href = `#${outputListHeaderId}`; + link.innerText = "Trait Implementations"; + const h = document.createElement("h3"); + h.appendChild(link); + trait_implementations = outputList; + sidebarSection.appendChild(h); + sidebarTraitList = document.createElement("ul"); + sidebarTraitList.className = "block trait-implementation"; + sidebarSection.appendChild(sidebarTraitList); + const mainContent = document.querySelector("#main-content"); + mainContent.appendChild(outputListH); + mainContent.appendChild(outputList); + } else { + implementations = outputList; + if (trait_implementations) { + document.insertBefore(outputListH, trait_implementations); + document.insertBefore(outputList, trait_implementations); + } else { + const mainContent = document.querySelector("#main-content"); + mainContent.appendChild(outputListH); + mainContent.appendChild(outputList); + } + } + } + const template = document.createElement("template"); + template.innerHTML = text; + + onEachLazy(template.content.querySelectorAll("a"), elem => { + const href = elem.getAttribute("href"); + + if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) { + elem.setAttribute("href", window.rootPath + href); + } + }); + onEachLazy(template.content.querySelectorAll("[id]"), el => { + let i = 0; + if (idMap.has(el.id)) { + i = idMap.get(el.id); + } else if (document.getElementById(el.id)) { + i = 1; + while (document.getElementById(`${el.id}-${2 * i}`)) { + i = 2 * i; + } + while (document.getElementById(`${el.id}-${i}`)) { + i += 1; + } + } + if (i !== 0) { + el.id = `${el.id}-${i}`; + } + idMap.set(el.id, i + 1); + }); + const templateAssocItems = template.content.querySelectorAll("section.tymethod, " + + "section.method, section.associatedtype, section.associatedconstant"); + if (isTrait) { + const li = document.createElement("li"); + const a = document.createElement("a"); + a.href = `#${template.content.querySelector(".impl").id}`; + a.textContent = traitName; + li.appendChild(a); + sidebarTraitList.append(li); + } else { + onEachLazy(templateAssocItems, item => { + let block = hasClass(item, "associatedtype") ? associatedTypes : ( + hasClass(item, "associatedconstant") ? associatedConstants : ( + methods)); + if (!block) { + const blockTitle = hasClass(item, "associatedtype") ? "Associated Types" : ( + hasClass(item, "associatedconstant") ? "Associated Constants" : ( + "Methods")); + const blockClass = hasClass(item, "associatedtype") ? "associatedtype" : ( + hasClass(item, "associatedconstant") ? "associatedconstant" : ( + "method")); + const blockH = document.createElement("h3"); + const blockA = document.createElement("a"); + blockA.href = "#implementations"; + blockA.innerText = blockTitle; + blockH.appendChild(blockA); + block = document.createElement("ul"); + block.className = `block ${blockClass}`; + const insertionReference = methods || sidebarTraitList; + if (insertionReference) { + const insertionReferenceH = insertionReference.previousElementSibling; + sidebarSection.insertBefore(blockH, insertionReferenceH); + sidebarSection.insertBefore(block, insertionReferenceH); + } else { + sidebarSection.appendChild(blockH); + sidebarSection.appendChild(block); + } + if (hasClass(item, "associatedtype")) { + associatedTypes = block; + } else if (hasClass(item, "associatedconstant")) { + associatedConstants = block; + } else { + methods = block; + } + } + const li = document.createElement("li"); + const a = document.createElement("a"); + a.innerText = item.id.split("-")[0].split(".")[1]; + a.href = `#${item.id}`; + li.appendChild(a); + block.appendChild(li); + }); + } + outputList.appendChild(template.content); + } + + for (const list of [methods, associatedTypes, associatedConstants, sidebarTraitList]) { + if (!list) { + continue; + } + const newChildren = Array.prototype.slice.call(list.children); + newChildren.sort((a, b) => { + const aI = a.innerText; + const bI = b.innerText; + return aI < bI ? -1 : + aI > bI ? 1 : + 0; + }); + list.replaceChildren(...newChildren); + } + }; + if (window.pending_type_impls) { + window.register_type_impls(window.pending_type_impls); + } + function addSidebarCrates() { if (!window.ALL_CRATES) { return; diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index a99198141e2..4318d680276 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -18,7 +18,11 @@

{{block.heading.name}}

{% endif %} {% if !block.links.is_empty() %} -
    +
      {% for link in block.links %}
    • {{link.name}}
    • {% endfor %} diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index ff89d4e0887..a57321b5822 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -36,7 +36,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> let prims: FxHashSet = local_crate.primitives(tcx).iter().map(|p| p.1).collect(); let crate_items = { - let mut coll = ItemCollector::new(); + let mut coll = ItemAndAliasCollector::new(&cx.cache); cx.sess().time("collect_items_for_trait_impls", || coll.visit_crate(&krate)); coll.items }; @@ -235,21 +235,27 @@ impl<'a, 'tcx> DocVisitor for SyntheticImplCollector<'a, 'tcx> { } } -#[derive(Default)] -struct ItemCollector { +struct ItemAndAliasCollector<'cache> { items: FxHashSet, + cache: &'cache Cache, } -impl ItemCollector { - fn new() -> Self { - Self::default() +impl<'cache> ItemAndAliasCollector<'cache> { + fn new(cache: &'cache Cache) -> Self { + ItemAndAliasCollector { items: FxHashSet::default(), cache } } } -impl DocVisitor for ItemCollector { +impl<'cache> DocVisitor for ItemAndAliasCollector<'cache> { fn visit_item(&mut self, i: &Item) { self.items.insert(i.item_id); + if let TypeAliasItem(alias) = &*i.kind && + let Some(did) = alias.type_.def_id(self.cache) + { + self.items.insert(ItemId::DefId(did)); + } + self.visit_item_recur(i) } } diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 5c91bcbb4ee..5b6d5435b35 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -160,6 +160,13 @@ pub mod keyword {} /// Just some type alias. pub type SomeType = u32; +/// Another type alias, this time with methods. +pub type SomeOtherTypeWithMethodsAndInlining = Foo; + +impl SomeOtherTypeWithMethodsAndInlining { + pub fn some_other_method_directly(&self) {} +} + pub mod huge_amount_of_consts { include!(concat!(env!("OUT_DIR"), "/huge_amount_of_consts.rs")); } diff --git a/tests/rustdoc-gui/type-impls.goml b/tests/rustdoc-gui/type-impls.goml new file mode 100644 index 00000000000..c7c9f54cad6 --- /dev/null +++ b/tests/rustdoc-gui/type-impls.goml @@ -0,0 +1,40 @@ +// The goal of this test is to check that the inlined type alias impls, generated with JS, +// have the same display than the "local" ones. +go-to: "file://" + |DOC_PATH| + "/test_docs/type.SomeOtherTypeWithMethodsAndInlining.html" + +// method directly on type alias +wait-for: "//*[@id='method.some_other_method_directly']" + +// methods on foo +assert: "//*[@id='method.must_use']" +assert: "//*[@id='method.warning1']" +assert: "//*[@id='method.warning2']" + +// sidebar items +assert: "//*[@class='sidebar-elems']//li/a[@href='#method.as_ref']" +assert: "//*[@class='sidebar-elems']//li/a[@href='#method.must_use']" +assert: "//*[@class='sidebar-elems']//li/a[@href='#method.some_other_method_directly']" +assert: "//*[@class='sidebar-elems']//li/a[@href='#method.warning1']" +assert: "//*[@class='sidebar-elems']//li/a[@href='#method.warning2']" +assert: "//*[@class='sidebar-elems']//li/a[@href='#impl-AsRef%3Cstr%3E-for-Foo']" + +// sorting +assert-text: (".block.method li:nth-child(1)", 'as_ref') +assert-text: (".block.method li:nth-child(2)", 'must_use') +assert-text: (".block.method li:nth-child(3)", 'some_other_method_directly') +assert-text: (".block.method li:nth-child(4)", 'warning1') +assert-text: (".block.method li:nth-child(5)", 'warning2') + +/////////////////////////////////////////////////////////////////////////// +// Now, if JavaScript is disabled, only the first method will be present // +/////////////////////////////////////////////////////////////////////////// +javascript: false +reload: + +// method directly on type alias +wait-for: "//*[@id='method.some_other_method_directly']" + +// methods on foo +assert-false: "//*[@id='method.must_use']" +assert-false: "//*[@id='method.warning1']" +assert-false: "//*[@id='method.warning2']" diff --git a/tests/rustdoc/deref/deref-mut-methods.rs b/tests/rustdoc/deref/deref-mut-methods.rs index fdf8434224f..65681f81245 100644 --- a/tests/rustdoc/deref/deref-mut-methods.rs +++ b/tests/rustdoc/deref/deref-mut-methods.rs @@ -9,7 +9,7 @@ impl Foo { } // @has foo/struct.Bar.html -// @has - '//*[@class="sidebar-elems"]//*[@class="block"]//a[@href="#method.foo"]' 'foo' +// @has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.foo"]' 'foo' pub struct Bar { foo: Foo, } diff --git a/tests/rustdoc/deref/deref-recursive-pathbuf.rs b/tests/rustdoc/deref/deref-recursive-pathbuf.rs index be2b42b5ac6..7aee3147ba8 100644 --- a/tests/rustdoc/deref/deref-recursive-pathbuf.rs +++ b/tests/rustdoc/deref/deref-recursive-pathbuf.rs @@ -8,9 +8,9 @@ // @has '-' '//*[@id="deref-methods-Path"]' 'Methods from Deref' // @has '-' '//*[@class="impl-items"]//*[@id="method.exists"]' 'pub fn exists(&self)' // @has '-' '//div[@class="sidebar-elems"]//h3/a[@href="#deref-methods-PathBuf"]' 'Methods from Deref' -// @has '-' '//*[@class="sidebar-elems"]//*[@class="block"]//a[@href="#method.as_path"]' 'as_path' +// @has '-' '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.as_path"]' 'as_path' // @has '-' '//div[@class="sidebar-elems"]//h3/a[@href="#deref-methods-Path"]' 'Methods from Deref' -// @has '-' '//*[@class="sidebar-elems"]//*[@class="block"]//a[@href="#method.exists"]' 'exists' +// @has '-' '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.exists"]' 'exists' #![crate_name = "foo"] diff --git a/tests/rustdoc/strip-enum-variant.no-not-shown.html b/tests/rustdoc/strip-enum-variant.no-not-shown.html index 782198956a0..e072335297d 100644 --- a/tests/rustdoc/strip-enum-variant.no-not-shown.html +++ b/tests/rustdoc/strip-enum-variant.no-not-shown.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc/strip-enum-variant.rs b/tests/rustdoc/strip-enum-variant.rs index 8753a7dc613..2512fa34b39 100644 --- a/tests/rustdoc/strip-enum-variant.rs +++ b/tests/rustdoc/strip-enum-variant.rs @@ -3,7 +3,7 @@ // @!has - '//code' 'NotShown' // @has - '//code' '// some variants omitted' // Also check that `NotShown` isn't displayed in the sidebar. -// @snapshot no-not-shown - '//*[@class="sidebar-elems"]/section/*[@class="block"][1]' +// @snapshot no-not-shown - '//*[@class="sidebar-elems"]/section/*[@class="block variant"]' pub enum MyThing { Shown, #[doc(hidden)] diff --git a/tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs b/tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs new file mode 100644 index 00000000000..3607612c27a --- /dev/null +++ b/tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs @@ -0,0 +1,9 @@ +pub struct MyStruct(T); + +pub trait MyTrait1 { + fn method_trait_1(); +} + +impl MyTrait1 for MyStruct { + fn method_trait_1() {} +} diff --git a/tests/rustdoc/type-alias/cross-crate-115718.rs b/tests/rustdoc/type-alias/cross-crate-115718.rs new file mode 100644 index 00000000000..372e62e4213 --- /dev/null +++ b/tests/rustdoc/type-alias/cross-crate-115718.rs @@ -0,0 +1,34 @@ +// aux-build: parent-crate-115718.rs + +// https://github.com/rust-lang/rust/issues/115718 +#![crate_name = "foo"] + +extern crate parent_crate_115718; + +use parent_crate_115718::MyStruct; + +pub trait MyTrait2 { + fn method_trait_2(); +} + +impl MyTrait2 for MyStruct { + fn method_trait_2() {} +} + +pub trait MyTrait3 { + fn method_trait_3(); +} + +impl MyTrait3 for MyType { + fn method_trait_3() {} +} + +// @hasraw 'type.impl/parent_crate_115718/struct.MyStruct.js' 'method_trait_1' +// @hasraw 'type.impl/parent_crate_115718/struct.MyStruct.js' 'method_trait_2' +// Avoid duplicating these docs. +// @!hasraw 'foo/type.MyType.html' 'method_trait_1' +// @!hasraw 'foo/type.MyType.html' 'method_trait_2' +// The one made directly on the type alias should be attached to the HTML instead. +// @!hasraw 'type.impl/parent_crate_115718/struct.MyStruct.js' 'method_trait_3' +// @hasraw 'foo/type.MyType.html' 'method_trait_3' +pub type MyType = MyStruct; diff --git a/tests/rustdoc/issue-112515-impl-ty-alias.rs b/tests/rustdoc/type-alias/deeply-nested-112515.rs similarity index 100% rename from tests/rustdoc/issue-112515-impl-ty-alias.rs rename to tests/rustdoc/type-alias/deeply-nested-112515.rs diff --git a/tests/rustdoc/type-alias/deref-32077.rs b/tests/rustdoc/type-alias/deref-32077.rs new file mode 100644 index 00000000000..740ed4cec70 --- /dev/null +++ b/tests/rustdoc/type-alias/deref-32077.rs @@ -0,0 +1,70 @@ +// Regression test for . + +#![crate_name = "foo"] + +pub struct GenericStruct(T); + +impl GenericStruct { + pub fn on_gen(arg: T) {} +} + +impl GenericStruct { + pub fn on_u32(arg: u32) {} +} + +pub trait Foo {} +pub trait Bar {} + +impl Foo for GenericStruct {} +impl Bar for GenericStruct {} + +// @has 'foo/type.TypedefStruct.html' +// We check that "Aliased type" is also present as a title in the sidebar. +// @has - '//*[@class="sidebar-elems"]//h3/a[@href="#aliased-type"]' 'Aliased type' +// We check that we have the implementation of the type alias itself. +// @has - '//*[@id="impl-TypedefStruct"]/h3' 'impl TypedefStruct' +// @has - '//*[@id="method.on_alias"]/h4' 'pub fn on_alias()' +// This trait implementation doesn't match the type alias parameters so shouldn't appear in docs. +// @!has - '//h3' 'impl Bar for GenericStruct {}' +// Same goes for the `Deref` impl. +// @!has - '//h2' 'Methods from Deref' +// @count - '//nav[@class="sidebar"]//a' 'on_alias' 1 +// @!has - '//nav[@class="sidebar"]//a' 'on_gen' +// @!has - '//nav[@class="sidebar"]//a' 'Foo' +// @!has - '//nav[@class="sidebar"]//a' 'Bar' +// @!has - '//nav[@class="sidebar"]//a' 'on_u32' +// TypedefStruct inlined to GenericStruct +// @hasraw 'type.impl/foo/struct.GenericStruct.js' 'TypedefStruct' +// @hasraw 'type.impl/foo/struct.GenericStruct.js' 'method.on_gen' +// @hasraw 'type.impl/foo/struct.GenericStruct.js' 'Foo' +pub type TypedefStruct = GenericStruct; + +impl TypedefStruct { + pub fn on_alias() {} +} + +impl std::ops::Deref for GenericStruct { + type Target = u32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct Wrap(GenericStruct); + +// @has 'foo/type.Alias.html' +// @!has - '//h2' 'Methods from Deref' +// @!has - '//*[@id="impl-Deref-for-Wrap%3CT%3E"]/h3' 'impl Deref for Wrap' +// @hasraw 'type.impl/foo/struct.Wrap.js' 'impl-Deref-for-Wrap%3CT%3E' +// Deref Methods aren't gathered for type aliases, though the actual impl is. +// @!hasraw 'type.impl/foo/struct.Wrap.js' 'BITS' +pub type Alias = Wrap; + +impl std::ops::Deref for Wrap { + type Target = GenericStruct; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/tests/rustdoc/type-alias/same-crate-115718.rs b/tests/rustdoc/type-alias/same-crate-115718.rs new file mode 100644 index 00000000000..26e5db85cd6 --- /dev/null +++ b/tests/rustdoc/type-alias/same-crate-115718.rs @@ -0,0 +1,34 @@ +// https://github.com/rust-lang/rust/issues/115718 +#![crate_name = "foo"] + +pub trait MyTrait1 { + fn method_trait_1(); +} + +pub trait MyTrait2 { + fn method_trait_2(); +} + +pub struct MyStruct(T); + +impl MyStruct { + pub fn method_u32() {} +} + +impl MyStruct { + pub fn method_u16() {} +} + +impl MyTrait1 for MyStruct { + fn method_trait_1() {} +} + +impl MyTrait2 for MyStruct { + fn method_trait_2() {} +} + +// @hasraw 'type.impl/foo/struct.MyStruct.js' 'method_u16' +// @!hasraw 'type.impl/foo/struct.MyStruct.js' 'method_u32' +// @!hasraw 'type.impl/foo/struct.MyStruct.js' 'method_trait_1' +// @hasraw 'type.impl/foo/struct.MyStruct.js' 'method_trait_2' +pub type MyType = MyStruct;