diff options
Diffstat (limited to 'src/librustdoc/html/render')
-rw-r--r-- | src/librustdoc/html/render/context.rs | 74 | ||||
-rw-r--r-- | src/librustdoc/html/render/mod.rs | 77 | ||||
-rw-r--r-- | src/librustdoc/html/render/print_item.rs | 449 | ||||
-rw-r--r-- | src/librustdoc/html/render/search_index.rs | 491 | ||||
-rw-r--r-- | src/librustdoc/html/render/sidebar.rs | 46 |
5 files changed, 717 insertions, 420 deletions
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index d7ff248a9..97714afaa 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -7,8 +7,10 @@ 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}; @@ -22,13 +24,13 @@ use super::{ sidebar::{sidebar_module_like, Sidebar}, AllTypes, LinkFromSrc, StylePath, }; -use crate::clean::{self, types::ExternalLocation, ExternalCrate}; +use crate::clean::{self, types::ExternalLocation, ExternalCrate, TypeAliasItem}; 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::FormatRenderer; +use crate::formats::{self, 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}; @@ -105,8 +107,8 @@ pub(crate) struct SharedContext<'tcx> { pub(super) module_sorting: ModuleSorting, /// Additional CSS files to be added to the generated docs. pub(crate) style_files: Vec<StylePath>, - /// Suffix to be added on resource files (if suffix is "-v2" then "light.css" becomes - /// "light-v2.css"). + /// Suffix to add on resource files (if suffix is "-v2" then "search-index.js" becomes + /// "search-index-v2.js"). pub(crate) resource_suffix: String, /// Optional path string to be used to load static files on output pages. If not set, uses /// combinations of `../` to reach the documentation root. @@ -147,6 +149,53 @@ 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> { @@ -463,6 +512,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { generate_link_to_definition, call_locations, no_emit_shared, + html_no_source, .. } = options; @@ -488,7 +538,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { scrape_examples_extension: !call_locations.is_empty(), }; let mut issue_tracker_base_url = None; - let mut include_sources = true; + let mut include_sources = !html_no_source; // Crawl the crate attributes looking for attributes which control how we're // going to emit HTML @@ -664,21 +714,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { You need to enable JavaScript be able to update your settings.\ </section>\ </noscript>\ - <link rel=\"stylesheet\" \ - href=\"{static_root_path}{settings_css}\">\ - <script defer src=\"{static_root_path}{settings_js}\"></script>\ - <link rel=\"preload\" href=\"{static_root_path}{theme_light_css}\" \ - as=\"style\">\ - <link rel=\"preload\" href=\"{static_root_path}{theme_dark_css}\" \ - as=\"style\">\ - <link rel=\"preload\" href=\"{static_root_path}{theme_ayu_css}\" \ - as=\"style\">", + <script defer src=\"{static_root_path}{settings_js}\"></script>", static_root_path = page.get_static_root_path(), - settings_css = static_files::STATIC_FILES.settings_css, settings_js = static_files::STATIC_FILES.settings_js, - theme_light_css = static_files::STATIC_FILES.theme_light_css, - theme_dark_css = static_files::STATIC_FILES.theme_dark_css, - theme_ayu_css = static_files::STATIC_FILES.theme_ayu_css, ); // Pre-load all theme CSS files, so that switching feels seamless. // diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index ac9c180a6..3e671a64b 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -101,7 +101,7 @@ pub(crate) struct IndexItem { pub(crate) path: String, pub(crate) desc: String, pub(crate) parent: Option<DefId>, - pub(crate) parent_idx: Option<usize>, + pub(crate) parent_idx: Option<isize>, pub(crate) search_type: Option<IndexItemFunctionType>, pub(crate) aliases: Box<[Symbol]>, pub(crate) deprecation: Option<Deprecation>, @@ -122,7 +122,10 @@ impl Serialize for RenderType { let id = match &self.id { // 0 is a sentinel, everything else is one-indexed None => 0, - Some(RenderTypeId::Index(idx)) => idx + 1, + // concrete type + Some(RenderTypeId::Index(idx)) if *idx >= 0 => idx + 1, + // generic type parameter + Some(RenderTypeId::Index(idx)) => *idx, _ => panic!("must convert render types to indexes before serializing"), }; if let Some(generics) = &self.generics { @@ -140,7 +143,7 @@ impl Serialize for RenderType { pub(crate) enum RenderTypeId { DefId(DefId), Primitive(clean::PrimitiveType), - Index(usize), + Index(isize), } /// Full type of functions/methods in the search index. @@ -148,6 +151,7 @@ pub(crate) enum RenderTypeId { pub(crate) struct IndexItemFunctionType { inputs: Vec<RenderType>, output: Vec<RenderType>, + where_clause: Vec<Vec<RenderType>>, } impl Serialize for IndexItemFunctionType { @@ -170,10 +174,17 @@ impl Serialize for IndexItemFunctionType { _ => seq.serialize_element(&self.inputs)?, } match &self.output[..] { - [] => {} + [] if self.where_clause.is_empty() => {} [one] if one.generics.is_none() => seq.serialize_element(one)?, _ => seq.serialize_element(&self.output)?, } + for constraint in &self.where_clause { + if let [one] = &constraint[..] && one.generics.is_none() { + seq.serialize_element(one)?; + } else { + seq.serialize_element(constraint)?; + } + } seq.end() } } @@ -235,7 +246,7 @@ struct AllTypes { traits: FxHashSet<ItemEntry>, macros: FxHashSet<ItemEntry>, functions: FxHashSet<ItemEntry>, - typedefs: FxHashSet<ItemEntry>, + type_aliases: FxHashSet<ItemEntry>, opaque_tys: FxHashSet<ItemEntry>, statics: FxHashSet<ItemEntry>, constants: FxHashSet<ItemEntry>, @@ -255,7 +266,7 @@ impl AllTypes { traits: new_set(100), macros: new_set(100), functions: new_set(100), - typedefs: new_set(100), + type_aliases: new_set(100), opaque_tys: new_set(100), statics: new_set(100), constants: new_set(100), @@ -279,7 +290,7 @@ impl AllTypes { ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)), ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)), ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)), - ItemType::Typedef => self.typedefs.insert(ItemEntry::new(new_url, name)), + ItemType::TypeAlias => self.type_aliases.insert(ItemEntry::new(new_url, name)), ItemType::OpaqueTy => self.opaque_tys.insert(ItemEntry::new(new_url, name)), ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)), ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)), @@ -317,8 +328,8 @@ impl AllTypes { if !self.functions.is_empty() { sections.insert(ItemSection::Functions); } - if !self.typedefs.is_empty() { - sections.insert(ItemSection::TypeDefinitions); + if !self.type_aliases.is_empty() { + sections.insert(ItemSection::TypeAliases); } if !self.opaque_tys.is_empty() { sections.insert(ItemSection::OpaqueTypes); @@ -374,7 +385,7 @@ impl AllTypes { print_entries(f, &self.attribute_macros, ItemSection::AttributeMacros); print_entries(f, &self.derive_macros, ItemSection::DeriveMacros); print_entries(f, &self.functions, ItemSection::Functions); - print_entries(f, &self.typedefs, ItemSection::TypeDefinitions); + print_entries(f, &self.type_aliases, ItemSection::TypeAliases); print_entries(f, &self.trait_aliases, ItemSection::TraitAliases); print_entries(f, &self.opaque_tys, ItemSection::OpaqueTypes); print_entries(f, &self.statics, ItemSection::Statics); @@ -403,7 +414,8 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String { error_codes: shared.codes, edition: shared.edition(), playground: &shared.playground, - heading_offset: HeadingOffset::H1 + heading_offset: HeadingOffset::H1, + custom_code_classes_in_docs: false, } .into_string() ) @@ -437,6 +449,7 @@ fn render_markdown<'a, 'cx: 'a>( heading_offset: HeadingOffset, ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { + let custom_code_classes_in_docs = cx.tcx().features().custom_code_classes_in_docs; write!( f, "<div class=\"docblock\">{}</div>", @@ -448,6 +461,7 @@ fn render_markdown<'a, 'cx: 'a>( edition: cx.shared.edition(), playground: &cx.shared.playground, heading_offset, + custom_code_classes_in_docs, } .into_string() ) @@ -1117,13 +1131,13 @@ pub(crate) fn render_all_impls( fn render_assoc_items<'a, 'cx: 'a>( cx: &'a mut Context<'cx>, containing_item: &'a clean::Item, - it: DefId, + did: DefId, what: AssocItemRender<'a>, ) -> impl fmt::Display + 'a + Captures<'cx> { let mut derefs = DefIdSet::default(); - derefs.insert(it); + derefs.insert(did); display_fn(move |f| { - render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs); + render_assoc_items_inner(f, cx, containing_item, did, what, &mut derefs); Ok(()) }) } @@ -1132,15 +1146,17 @@ fn render_assoc_items_inner( mut w: &mut dyn fmt::Write, cx: &mut Context<'_>, containing_item: &clean::Item, - it: DefId, + did: DefId, what: AssocItemRender<'_>, derefs: &mut DefIdSet, ) { info!("Documenting associated items of {:?}", containing_item.name); let shared = Rc::clone(&cx.shared); - 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()); + 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(); if !non_trait.is_empty() { let mut tmp_buf = Buffer::html(); let (render_mode, id, class_html) = match what { @@ -1169,6 +1185,9 @@ 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, @@ -1214,8 +1233,10 @@ 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().partition(|t| t.inner_impl().kind.is_blanket()); + 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()); render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl); } @@ -1237,7 +1258,7 @@ fn render_deref_methods( .iter() .find_map(|item| match *item.kind { clean::AssocTypeItem(box ref t, _) => Some(match *t { - clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), + clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_), _ => (&t.type_, &t.type_), }), _ => None, @@ -1771,6 +1792,7 @@ fn render_impl( </div>", ); } + let custom_code_classes_in_docs = cx.tcx().features().custom_code_classes_in_docs; write!( w, "<div class=\"docblock\">{}</div>", @@ -1781,7 +1803,8 @@ fn render_impl( error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, - heading_offset: HeadingOffset::H4 + heading_offset: HeadingOffset::H4, + custom_code_classes_in_docs, } .into_string() ); @@ -2035,7 +2058,7 @@ pub(crate) enum ItemSection { Statics, Traits, Functions, - TypeDefinitions, + TypeAliases, Unions, Implementations, TypeMethods, @@ -2067,7 +2090,7 @@ impl ItemSection { Statics, Traits, Functions, - TypeDefinitions, + TypeAliases, Unions, Implementations, TypeMethods, @@ -2093,7 +2116,7 @@ impl ItemSection { Self::Unions => "unions", Self::Enums => "enums", Self::Functions => "functions", - Self::TypeDefinitions => "types", + Self::TypeAliases => "types", Self::Statics => "statics", Self::Constants => "constants", Self::Traits => "traits", @@ -2123,7 +2146,7 @@ impl ItemSection { Self::Unions => "Unions", Self::Enums => "Enums", Self::Functions => "Functions", - Self::TypeDefinitions => "Type Definitions", + Self::TypeAliases => "Type Aliases", Self::Statics => "Statics", Self::Constants => "Constants", Self::Traits => "Traits", @@ -2154,7 +2177,7 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::Union => ItemSection::Unions, ItemType::Enum => ItemSection::Enums, ItemType::Function => ItemSection::Functions, - ItemType::Typedef => ItemSection::TypeDefinitions, + ItemType::TypeAlias => ItemSection::TypeAliases, ItemType::Static => ItemSection::Statics, ItemType::Constant => ItemSection::Constants, ItemType::Trait => ItemSection::Traits, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6cab34986..c6751c958 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -198,7 +198,7 @@ pub(super) fn print_item( clean::StructItem(..) => "Struct ", clean::UnionItem(..) => "Union ", clean::EnumItem(..) => "Enum ", - clean::TypedefItem(..) => "Type Definition ", + clean::TypeAliasItem(..) => "Type Alias ", clean::MacroItem(..) => "Macro ", clean::ProcMacroItem(ref mac) => match mac.kind { MacroKind::Bang => "Macro ", @@ -273,7 +273,7 @@ pub(super) fn print_item( clean::StructItem(ref s) => item_struct(buf, cx, item, s), clean::UnionItem(ref s) => item_union(buf, cx, item, s), clean::EnumItem(ref e) => item_enum(buf, cx, item, e), - clean::TypedefItem(ref t) => item_typedef(buf, cx, item, t), + clean::TypeAliasItem(ref t) => item_type_alias(buf, cx, item, t), clean::MacroItem(ref m) => item_macro(buf, cx, item, m), clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m), clean::PrimitiveItem(_) => item_primitive(buf, cx, item), @@ -343,7 +343,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: ItemType::Static => 8, ItemType::Trait => 9, ItemType::Function => 10, - ItemType::Typedef => 12, + ItemType::TypeAlias => 12, ItemType::Union => 13, _ => 14 + ty as u8, } @@ -1217,8 +1217,8 @@ fn item_opaque_ty( .unwrap(); } -fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) { - fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) { +fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TypeAlias) { + fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) { wrap_item(w, |w| { write!( w, @@ -1237,6 +1237,75 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + if let Some(inner_type) = &t.inner_type { + write!( + w, + "<h2 id=\"aliased-type\" class=\"small-section-header\">\ + Aliased Type<a href=\"#aliased-type\" class=\"anchor\">§</a></h2>" + ); + + match inner_type { + clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => { + let variants_iter = || variants.iter().filter(|i| !i.is_stripped()); + wrap_item(w, |w| { + let variants_len = variants.len(); + let variants_count = variants_iter().count(); + let has_stripped_entries = variants_len != variants_count; + + write!(w, "enum {}{}", it.name.unwrap(), t.generics.print(cx)); + render_enum_fields( + w, + cx, + Some(&t.generics), + variants_iter(), + variants_count, + has_stripped_entries, + *is_non_exhaustive, + ) + }); + item_variants(w, cx, it, variants_iter()); + } + clean::TypeAliasInnerType::Union { fields } => { + wrap_item(w, |w| { + let fields_count = fields.iter().filter(|i| !i.is_stripped()).count(); + let has_stripped_fields = fields.len() != fields_count; + + write!(w, "union {}{}", it.name.unwrap(), t.generics.print(cx)); + render_struct_fields( + w, + Some(&t.generics), + None, + fields, + "", + true, + has_stripped_fields, + cx, + ); + }); + item_fields(w, cx, it, fields, None); + } + clean::TypeAliasInnerType::Struct { ctor_kind, fields } => { + wrap_item(w, |w| { + let fields_count = fields.iter().filter(|i| !i.is_stripped()).count(); + let has_stripped_fields = fields.len() != fields_count; + + write!(w, "struct {}{}", it.name.unwrap(), t.generics.print(cx)); + render_struct_fields( + w, + Some(&t.generics), + *ctor_kind, + fields, + "", + true, + has_stripped_fields, + cx, + ); + }); + item_fields(w, cx, it, fields, None); + } + } + } + let def_id = it.item_id.expect_def_id(); // Render any items associated directly to this alias, as otherwise they // won't be visible anywhere in the docs. It would be nice to also show @@ -1315,6 +1384,12 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>( s: &'a [clean::Item], ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(|f| { + if s.iter() + .all(|field| matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))) + { + return f.write_str("/* private fields */"); + } + for (i, ty) in s.iter().enumerate() { if i > 0 { f.write_str(", ")?; @@ -1332,7 +1407,7 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>( fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) { let tcx = cx.tcx(); let count_variants = e.variants().count(); - wrap_item(w, |mut w| { + wrap_item(w, |w| { render_attributes_in_code(w, it, tcx); write!( w, @@ -1341,148 +1416,179 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: it.name.unwrap(), e.generics.print(cx), ); - if !print_where_clause_and_check(w, &e.generics, cx) { - // If there wasn't a `where` clause, we add a whitespace. - w.write_str(" "); - } + render_enum_fields( + w, + cx, + Some(&e.generics), + e.variants(), + count_variants, + e.has_stripped_entries(), + it.is_non_exhaustive(), + ); + }); - let variants_stripped = e.has_stripped_entries(); - if count_variants == 0 && !variants_stripped { - w.write_str("{}"); - } else { - w.write_str("{\n"); - let toggle = should_hide_fields(count_variants); - if toggle { - toggle_open(&mut w, format_args!("{count_variants} variants")); - } - for v in e.variants() { - w.write_str(" "); - let name = v.name.unwrap(); - match *v.kind { - // FIXME(#101337): Show discriminant - clean::VariantItem(ref var) => match var.kind { - clean::VariantKind::CLike => w.write_str(name.as_str()), - clean::VariantKind::Tuple(ref s) => { - write!(w, "{name}({})", print_tuple_struct_fields(cx, s),); - } - clean::VariantKind::Struct(ref s) => { - render_struct(w, v, None, None, &s.fields, " ", false, cx); - } - }, - _ => unreachable!(), - } - w.write_str(",\n"); - } + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); - if variants_stripped && !it.is_non_exhaustive() { - w.write_str(" // some variants omitted\n"); - } - if toggle { - toggle_close(&mut w); + if count_variants != 0 { + item_variants(w, cx, it, e.variants()); + } + let def_id = it.item_id.expect_def_id(); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); +} + +fn render_enum_fields<'a>( + mut w: &mut Buffer, + cx: &mut Context<'_>, + g: Option<&clean::Generics>, + variants: impl Iterator<Item = &'a clean::Item>, + count_variants: usize, + has_stripped_entries: bool, + is_non_exhaustive: bool, +) { + if !g.is_some_and(|g| print_where_clause_and_check(w, g, cx)) { + // If there wasn't a `where` clause, we add a whitespace. + w.write_str(" "); + } + + let variants_stripped = has_stripped_entries; + if count_variants == 0 && !variants_stripped { + w.write_str("{}"); + } else { + w.write_str("{\n"); + let toggle = should_hide_fields(count_variants); + if toggle { + toggle_open(&mut w, format_args!("{count_variants} variants")); + } + const TAB: &str = " "; + for v in variants { + w.write_str(TAB); + let name = v.name.unwrap(); + match *v.kind { + // FIXME(#101337): Show discriminant + clean::VariantItem(ref var) => match var.kind { + clean::VariantKind::CLike => w.write_str(name.as_str()), + clean::VariantKind::Tuple(ref s) => { + write!(w, "{name}({})", print_tuple_struct_fields(cx, s),); + } + clean::VariantKind::Struct(ref s) => { + render_struct(w, v, None, None, &s.fields, TAB, false, cx); + } + }, + _ => unreachable!(), } - w.write_str("}"); + w.write_str(",\n"); } - }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + if variants_stripped && !is_non_exhaustive { + w.write_str(" // some variants omitted\n"); + } + if toggle { + toggle_close(&mut w); + } + w.write_str("}"); + } +} - if count_variants != 0 { +fn item_variants<'a>( + w: &mut Buffer, + cx: &mut Context<'_>, + it: &clean::Item, + variants: impl Iterator<Item = &'a clean::Item>, +) { + let tcx = cx.tcx(); + write!( + w, + "<h2 id=\"variants\" class=\"variants small-section-header\">\ + Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\ + </h2>\ + {}\ + <div class=\"variants\">", + document_non_exhaustive_header(it), + document_non_exhaustive(it) + ); + for variant in variants { + let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); write!( w, - "<h2 id=\"variants\" class=\"variants small-section-header\">\ - Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\ - </h2>\ - {}\ - <div class=\"variants\">", - document_non_exhaustive_header(it), - document_non_exhaustive(it) + "<section id=\"{id}\" class=\"variant\">\ + <a href=\"#{id}\" class=\"anchor\">§</a>", ); - for variant in e.variants() { - let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); - write!( - w, - "<section id=\"{id}\" class=\"variant\">\ - <a href=\"#{id}\" class=\"anchor\">§</a>", - ); - render_stability_since_raw_with_extra( - w, - variant.stable_since(tcx), - variant.const_stability(tcx), - it.stable_since(tcx), - it.const_stable_since(tcx), - " rightside", - ); - write!(w, "<h3 class=\"code-header\">{name}", name = variant.name.unwrap()); + render_stability_since_raw_with_extra( + w, + variant.stable_since(tcx), + variant.const_stability(tcx), + it.stable_since(tcx), + it.const_stable_since(tcx), + " rightside", + ); + write!(w, "<h3 class=\"code-header\">{name}", name = variant.name.unwrap()); - let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() }; + let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() }; - if let clean::VariantKind::Tuple(ref s) = variant_data.kind { - write!(w, "({})", print_tuple_struct_fields(cx, s)); - } - w.write_str("</h3></section>"); - - let heading_and_fields = match &variant_data.kind { - clean::VariantKind::Struct(s) => Some(("Fields", &s.fields)), - clean::VariantKind::Tuple(fields) => { - // Documentation on tuple variant fields is rare, so to reduce noise we only emit - // the section if at least one field is documented. - if fields.iter().any(|f| !f.doc_value().is_empty()) { - Some(("Tuple Fields", fields)) - } else { - None - } + if let clean::VariantKind::Tuple(ref s) = variant_data.kind { + write!(w, "({})", print_tuple_struct_fields(cx, s)); + } + w.write_str("</h3></section>"); + + let heading_and_fields = match &variant_data.kind { + clean::VariantKind::Struct(s) => Some(("Fields", &s.fields)), + clean::VariantKind::Tuple(fields) => { + // Documentation on tuple variant fields is rare, so to reduce noise we only emit + // the section if at least one field is documented. + if fields.iter().any(|f| !f.doc_value().is_empty()) { + Some(("Tuple Fields", fields)) + } else { + None } - clean::VariantKind::CLike => None, - }; + } + clean::VariantKind::CLike => None, + }; - if let Some((heading, fields)) = heading_and_fields { - let variant_id = - cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap())); - write!( - w, - "<div class=\"sub-variant\" id=\"{variant_id}\">\ - <h4>{heading}</h4>\ - {}", - document_non_exhaustive(variant) - ); - for field in fields { - match *field.kind { - clean::StrippedItem(box clean::StructFieldItem(_)) => {} - clean::StructFieldItem(ref ty) => { - let id = cx.derive_id(format!( - "variant.{}.field.{}", - variant.name.unwrap(), - field.name.unwrap() - )); - write!( - w, - "<div class=\"sub-variant-field\">\ + if let Some((heading, fields)) = heading_and_fields { + let variant_id = + cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap())); + write!( + w, + "<div class=\"sub-variant\" id=\"{variant_id}\">\ + <h4>{heading}</h4>\ + {}", + document_non_exhaustive(variant) + ); + for field in fields { + match *field.kind { + clean::StrippedItem(box clean::StructFieldItem(_)) => {} + clean::StructFieldItem(ref ty) => { + let id = cx.derive_id(format!( + "variant.{}.field.{}", + variant.name.unwrap(), + field.name.unwrap() + )); + write!( + w, + "<div class=\"sub-variant-field\">\ <span id=\"{id}\" class=\"small-section-header\">\ <a href=\"#{id}\" class=\"anchor field\">§</a>\ <code>{f}: {t}</code>\ </span>", - f = field.name.unwrap(), - t = ty.print(cx), - ); - write!( - w, - "{}</div>", - document(cx, field, Some(variant), HeadingOffset::H5) - ); - } - _ => unreachable!(), + f = field.name.unwrap(), + t = ty.print(cx), + ); + write!( + w, + "{}</div>", + document(cx, field, Some(variant), HeadingOffset::H5) + ); } + _ => unreachable!(), } - w.write_str("</div>"); } - - write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4)); + w.write_str("</div>"); } - write!(w, "</div>"); + + write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4)); } - let def_id = it.item_id.expect_def_id(); - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); - write!(w, "{}", document_type_layout(cx, def_id)); + write!(w, "</div>"); } fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) { @@ -1593,15 +1699,28 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); - let mut fields = s - .fields + item_fields(w, cx, it, &s.fields, s.ctor_kind); + + let def_id = it.item_id.expect_def_id(); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); +} + +fn item_fields( + w: &mut Buffer, + cx: &mut Context<'_>, + it: &clean::Item, + fields: &Vec<clean::Item>, + ctor_kind: Option<CtorKind>, +) { + let mut fields = fields .iter() .filter_map(|f| match *f.kind { clean::StructFieldItem(ref ty) => Some((f, ty)), _ => None, }) .peekable(); - if let None | Some(CtorKind::Fn) = s.ctor_kind { + if let None | Some(CtorKind::Fn) = ctor_kind { if fields.peek().is_some() { write!( w, @@ -1609,7 +1728,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean {}{}<a href=\"#fields\" class=\"anchor\">§</a>\ </h2>\ {}", - if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, + if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, document_non_exhaustive_header(it), document_non_exhaustive(it) ); @@ -1630,9 +1749,6 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean } } } - let def_id = it.item_id.expect_def_id(); - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); - write!(w, "{}", document_type_layout(cx, def_id)); } fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { @@ -1871,7 +1987,7 @@ fn render_union<'a, 'cx: 'a>( } fn render_struct( - mut w: &mut Buffer, + w: &mut Buffer, it: &clean::Item, g: Option<&clean::Generics>, ty: Option<CtorKind>, @@ -1891,6 +2007,29 @@ fn render_struct( if let Some(g) = g { write!(w, "{}", g.print(cx)) } + render_struct_fields( + w, + g, + ty, + fields, + tab, + structhead, + it.has_stripped_entries().unwrap_or(false), + cx, + ) +} + +fn render_struct_fields( + mut w: &mut Buffer, + g: Option<&clean::Generics>, + ty: Option<CtorKind>, + fields: &[clean::Item], + tab: &str, + structhead: bool, + has_stripped_entries: bool, + cx: &Context<'_>, +) { + let tcx = cx.tcx(); match ty { None => { let where_displayed = @@ -1922,11 +2061,11 @@ fn render_struct( } if has_visible_fields { - if it.has_stripped_entries().unwrap() { + if has_stripped_entries { write!(w, "\n{tab} /* private fields */"); } write!(w, "\n{tab}"); - } else if it.has_stripped_entries().unwrap() { + } else if has_stripped_entries { write!(w, " /* private fields */ "); } if toggle { @@ -1936,21 +2075,31 @@ fn render_struct( } Some(CtorKind::Fn) => { w.write_str("("); - for (i, field) in fields.iter().enumerate() { - if i > 0 { - w.write_str(", "); - } - match *field.kind { - clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), - clean::StructFieldItem(ref ty) => { - write!( - w, - "{}{}", - visibility_print_with_space(field.visibility(tcx), field.item_id, cx), - ty.print(cx), - ) + if fields.iter().all(|field| { + matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) + }) { + write!(w, "/* private fields */"); + } else { + for (i, field) in fields.iter().enumerate() { + if i > 0 { + w.write_str(", "); + } + match *field.kind { + clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), + clean::StructFieldItem(ref ty) => { + write!( + w, + "{}{}", + visibility_print_with_space( + field.visibility(tcx), + field.item_id, + cx + ), + ty.print(cx), + ) + } + _ => unreachable!(), } - _ => unreachable!(), } } w.write_str(")"); diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index f34be120d..78c443b22 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -1,10 +1,10 @@ use std::collections::hash_map::Entry; use std::collections::BTreeMap; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::Symbol; -use serde::ser::{Serialize, SerializeStruct, Serializer}; +use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer}; use crate::clean; use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate}; @@ -68,19 +68,19 @@ pub(crate) fn build_index<'tcx>( // Reduce `DefId` in paths into smaller sequential numbers, // and prune the paths that do not appear in the index. let mut lastpath = ""; - let mut lastpathid = 0usize; + let mut lastpathid = 0isize; // First, on function signatures let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new()); for item in search_index.iter_mut() { fn insert_into_map<F: std::hash::Hash + Eq>( ty: &mut RenderType, - map: &mut FxHashMap<F, usize>, + map: &mut FxHashMap<F, isize>, itemid: F, - lastpathid: &mut usize, - crate_paths: &mut Vec<(ItemType, Symbol)>, + lastpathid: &mut isize, + crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, item_type: ItemType, - path: Symbol, + path: &[Symbol], ) { match map.entry(itemid) { Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())), @@ -88,7 +88,7 @@ pub(crate) fn build_index<'tcx>( let pathid = *lastpathid; entry.insert(pathid); *lastpathid += 1; - crate_paths.push((item_type, path)); + crate_paths.push((item_type, path.to_vec())); ty.id = Some(RenderTypeId::Index(pathid)); } } @@ -97,10 +97,10 @@ pub(crate) fn build_index<'tcx>( fn convert_render_type( ty: &mut RenderType, cache: &mut Cache, - itemid_to_pathid: &mut FxHashMap<ItemId, usize>, - primitives: &mut FxHashMap<Symbol, usize>, - lastpathid: &mut usize, - crate_paths: &mut Vec<(ItemType, Symbol)>, + itemid_to_pathid: &mut FxHashMap<ItemId, isize>, + primitives: &mut FxHashMap<Symbol, isize>, + lastpathid: &mut isize, + crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, ) { if let Some(generics) = &mut ty.generics { for item in generics { @@ -131,7 +131,7 @@ pub(crate) fn build_index<'tcx>( lastpathid, crate_paths, item_type, - *fqp.last().unwrap(), + fqp, ); } else { ty.id = None; @@ -146,7 +146,7 @@ pub(crate) fn build_index<'tcx>( lastpathid, crate_paths, ItemType::Primitive, - sym, + &[sym], ); } RenderTypeId::Index(_) => {} @@ -173,6 +173,18 @@ pub(crate) fn build_index<'tcx>( &mut crate_paths, ); } + for constraint in &mut search_type.where_clause { + for trait_ in &mut constraint[..] { + convert_render_type( + trait_, + cache, + &mut itemid_to_pathid, + &mut primitives, + &mut lastpathid, + &mut crate_paths, + ); + } + } } } @@ -191,7 +203,7 @@ pub(crate) fn build_index<'tcx>( lastpathid += 1; if let Some(&(ref fqp, short)) = paths.get(&defid) { - crate_paths.push((short, *fqp.last().unwrap())); + crate_paths.push((short, fqp.clone())); Some(pathid) } else { None @@ -213,118 +225,163 @@ pub(crate) fn build_index<'tcx>( struct CrateData<'a> { doc: String, items: Vec<&'a IndexItem>, - paths: Vec<(ItemType, Symbol)>, + paths: Vec<(ItemType, Vec<Symbol>)>, // The String is alias name and the vec is the list of the elements with this alias. // // To be noted: the `usize` elements are indexes to `items`. aliases: &'a BTreeMap<String, Vec<usize>>, } + struct Paths { + ty: ItemType, + name: Symbol, + path: Option<usize>, + } + + impl Serialize for Paths { + 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.ty)?; + seq.serialize_element(self.name.as_str())?; + if let Some(ref path) = self.path { + seq.serialize_element(path)?; + } + seq.end() + } + } + impl<'a> Serialize for CrateData<'a> { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { + let mut extra_paths = FxHashMap::default(); + // We need to keep the order of insertion, hence why we use an `IndexMap`. Then we will + // insert these "extra paths" (which are paths of items from external crates) into the + // `full_paths` list at the end. + let mut revert_extra_paths = FxIndexMap::default(); + let mut mod_paths = FxHashMap::default(); + for (index, item) in self.items.iter().enumerate() { + if item.path.is_empty() { + continue; + } + mod_paths.insert(&item.path, index); + } + let mut paths = Vec::with_capacity(self.paths.len()); + for (ty, path) in &self.paths { + if path.len() < 2 { + paths.push(Paths { ty: *ty, name: path[0], path: None }); + continue; + } + let full_path = join_with_double_colon(&path[..path.len() - 1]); + if let Some(index) = mod_paths.get(&full_path) { + paths.push(Paths { ty: *ty, name: *path.last().unwrap(), path: Some(*index) }); + continue; + } + // It means it comes from an external crate so the item and its path will be + // stored into another array. + // + // `index` is put after the last `mod_paths` + let index = extra_paths.len() + self.items.len(); + if !revert_extra_paths.contains_key(&index) { + revert_extra_paths.insert(index, full_path.clone()); + } + match extra_paths.entry(full_path) { + Entry::Occupied(entry) => { + paths.push(Paths { + ty: *ty, + name: *path.last().unwrap(), + path: Some(*entry.get()), + }); + } + Entry::Vacant(entry) => { + entry.insert(index); + paths.push(Paths { + ty: *ty, + name: *path.last().unwrap(), + path: Some(index), + }); + } + } + } + + let mut names = Vec::with_capacity(self.items.len()); + let mut types = String::with_capacity(self.items.len()); + let mut full_paths = Vec::with_capacity(self.items.len()); + let mut descriptions = Vec::with_capacity(self.items.len()); + let mut parents = Vec::with_capacity(self.items.len()); + let mut functions = Vec::with_capacity(self.items.len()); + let mut deprecated = Vec::with_capacity(self.items.len()); + + for (index, item) in self.items.iter().enumerate() { + let n = item.ty as u8; + let c = char::try_from(n + b'A').expect("item types must fit in ASCII"); + assert!(c <= 'z', "item types must fit within ASCII printables"); + types.push(c); + + assert_eq!( + item.parent.is_some(), + item.parent_idx.is_some(), + "`{}` is missing idx", + item.name + ); + // 0 is a sentinel, everything else is one-indexed + parents.push(item.parent_idx.map(|x| x + 1).unwrap_or(0)); + + names.push(item.name.as_str()); + descriptions.push(&item.desc); + + if !item.path.is_empty() { + full_paths.push((index, &item.path)); + } + + // Fake option to get `0` out as a sentinel instead of `null`. + // We want to use `0` because it's three less bytes. + enum FunctionOption<'a> { + Function(&'a IndexItemFunctionType), + None, + } + impl<'a> Serialize for FunctionOption<'a> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + match self { + FunctionOption::None => 0.serialize(serializer), + FunctionOption::Function(ty) => ty.serialize(serializer), + } + } + } + functions.push(match &item.search_type { + Some(ty) => FunctionOption::Function(ty), + None => FunctionOption::None, + }); + + if item.deprecation.is_some() { + deprecated.push(index); + } + } + + for (index, path) in &revert_extra_paths { + full_paths.push((*index, path)); + } + let has_aliases = !self.aliases.is_empty(); let mut crate_data = serializer.serialize_struct("CrateData", if has_aliases { 9 } else { 8 })?; crate_data.serialize_field("doc", &self.doc)?; - crate_data.serialize_field( - "t", - &self - .items - .iter() - .map(|item| { - let n = item.ty as u8; - let c = char::try_from(n + b'A').expect("item types must fit in ASCII"); - assert!(c <= 'z', "item types must fit within ASCII printables"); - c - }) - .collect::<String>(), - )?; - crate_data.serialize_field( - "n", - &self.items.iter().map(|item| item.name.as_str()).collect::<Vec<_>>(), - )?; - crate_data.serialize_field( - "q", - &self - .items - .iter() - .enumerate() - // Serialize as an array of item indices and full paths - .filter_map( - |(index, item)| { - if item.path.is_empty() { None } else { Some((index, &item.path)) } - }, - ) - .collect::<Vec<_>>(), - )?; - crate_data.serialize_field( - "d", - &self.items.iter().map(|item| &item.desc).collect::<Vec<_>>(), - )?; - crate_data.serialize_field( - "i", - &self - .items - .iter() - .map(|item| { - assert_eq!( - item.parent.is_some(), - item.parent_idx.is_some(), - "`{}` is missing idx", - item.name - ); - // 0 is a sentinel, everything else is one-indexed - item.parent_idx.map(|x| x + 1).unwrap_or(0) - }) - .collect::<Vec<_>>(), - )?; - crate_data.serialize_field( - "f", - &self - .items - .iter() - .map(|item| { - // Fake option to get `0` out as a sentinel instead of `null`. - // We want to use `0` because it's three less bytes. - enum FunctionOption<'a> { - Function(&'a IndexItemFunctionType), - None, - } - impl<'a> Serialize for FunctionOption<'a> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - match self { - FunctionOption::None => 0.serialize(serializer), - FunctionOption::Function(ty) => ty.serialize(serializer), - } - } - } - match &item.search_type { - Some(ty) => FunctionOption::Function(ty), - None => FunctionOption::None, - } - }) - .collect::<Vec<_>>(), - )?; - crate_data.serialize_field( - "c", - &self - .items - .iter() - .enumerate() - // Serialize as an array of deprecated item indices - .filter_map(|(index, item)| item.deprecation.map(|_| index)) - .collect::<Vec<_>>(), - )?; - crate_data.serialize_field( - "p", - &self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::<Vec<_>>(), - )?; + crate_data.serialize_field("t", &types)?; + crate_data.serialize_field("n", &names)?; + // Serialize as an array of item indices and full paths + crate_data.serialize_field("q", &full_paths)?; + crate_data.serialize_field("d", &descriptions)?; + crate_data.serialize_field("i", &parents)?; + crate_data.serialize_field("f", &functions)?; + crate_data.serialize_field("c", &deprecated)?; + crate_data.serialize_field("p", &paths)?; if has_aliases { crate_data.serialize_field("a", &self.aliases)?; } @@ -357,7 +414,7 @@ pub(crate) fn get_function_type_for_search<'tcx>( impl_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, ) -> Option<IndexItemFunctionType> { - let (mut inputs, mut output) = match *item.kind { + let (mut inputs, mut output, where_clause) = match *item.kind { clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache), clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), @@ -367,7 +424,7 @@ pub(crate) fn get_function_type_for_search<'tcx>( inputs.retain(|a| a.id.is_some() || a.generics.is_some()); output.retain(|a| a.id.is_some() || a.generics.is_some()); - Some(IndexItemFunctionType { inputs, output }) + Some(IndexItemFunctionType { inputs, output, where_clause }) } fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType { @@ -387,96 +444,48 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> { clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => { get_index_type_id(type_) } - // The type parameters are converted to generics in `add_generics_and_bounds_as_types` + // The type parameters are converted to generics in `simplify_fn_type` clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)), clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)), + clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)), // Not supported yet clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) - | clean::Tuple(_) | clean::QPath { .. } | clean::Infer => None, } } -/// The point of this function is to replace bounds with types. +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +enum SimplifiedParam { + // other kinds of type parameters are identified by their name + Symbol(Symbol), + // every argument-position impl trait is its own type parameter + Anonymous(isize), +} + +/// The point of this function is to lower generics and types into the simplified form that the +/// frontend search engine can use. /// -/// i.e. `[T, U]` when you have the following bounds: `T: Display, U: Option<T>` will return -/// `[Display, Option]`. If a type parameter has no trait bound, it is discarded. +/// For example, `[T, U, i32]]` where you have the bounds: `T: Display, U: Option<T>` will return +/// `[-1, -2, i32] where -1: Display, -2: Option<-1>`. If a type parameter has no traid bound, it +/// will still get a number. If a constraint is present but not used in the actual types, it will +/// not be added to the map. /// -/// Important note: It goes through generics recursively. So if you have -/// `T: Option<Result<(), ()>>`, it'll go into `Option` and then into `Result`. -#[instrument(level = "trace", skip(tcx, res, cache))] -fn add_generics_and_bounds_as_types<'tcx, 'a>( +/// This function also works recursively. +#[instrument(level = "trace", skip(tcx, res, rgen, cache))] +fn simplify_fn_type<'tcx, 'a>( self_: Option<&'a Type>, generics: &Generics, arg: &'a Type, tcx: TyCtxt<'tcx>, recurse: usize, res: &mut Vec<RenderType>, + rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, + is_return: bool, cache: &Cache, ) { - fn insert_ty(res: &mut Vec<RenderType>, ty: Type, mut generics: Vec<RenderType>) { - // generics and impl trait are both identified by their generics, - // rather than a type name itself - let anonymous = ty.is_full_generic() || ty.is_impl_trait(); - let generics_empty = generics.is_empty(); - - if anonymous { - if generics_empty { - // This is a type parameter with no trait bounds (for example: `T` in - // `fn f<T>(p: T)`, so not useful for the rustdoc search because we would end up - // with an empty type with an empty name. Let's just discard it. - return; - } else if generics.len() == 1 { - // In this case, no need to go through an intermediate state if the type parameter - // contains only one trait bound. - // - // For example: - // - // `fn foo<T: Display>(r: Option<T>) {}` - // - // In this case, it would contain: - // - // ``` - // [{ - // name: "option", - // generics: [{ - // name: "", - // generics: [ - // name: "Display", - // generics: [] - // }] - // }] - // }] - // ``` - // - // After removing the intermediate (unnecessary) type parameter, it'll become: - // - // ``` - // [{ - // name: "option", - // generics: [{ - // name: "Display", - // generics: [] - // }] - // }] - // ``` - // - // To be noted that it can work if there is ONLY ONE trait bound, otherwise we still - // need to keep it as is! - res.push(generics.pop().unwrap()); - return; - } - } - let index_ty = get_index_type(&ty, generics); - if index_ty.id.is_none() && generics_empty { - return; - } - res.push(index_ty); - } - if recurse >= 10 { // FIXME: remove this whole recurse thing when the recursion bug is fixed // See #59502 for the original issue. @@ -503,88 +512,126 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>( // for its bounds. if let Type::Generic(arg_s) = *arg { // First we check if the bounds are in a `where` predicate... + let mut type_bounds = Vec::new(); for where_pred in generics.where_predicates.iter().filter(|g| match g { WherePredicate::BoundPredicate { ty: Type::Generic(ty_s), .. } => *ty_s == arg_s, _ => false, }) { - let mut ty_generics = Vec::new(); let bounds = where_pred.get_bounds().unwrap_or_else(|| &[]); for bound in bounds.iter() { if let Some(path) = bound.get_trait_path() { let ty = Type::Path { path }; - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, - &mut ty_generics, + &mut type_bounds, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); } // Otherwise we check if the trait bounds are "inlined" like `T: Option<u32>`... if let Some(bound) = generics.params.iter().find(|g| g.is_type() && g.name == arg_s) { - let mut ty_generics = Vec::new(); for bound in bound.get_bounds().unwrap_or(&[]) { if let Some(path) = bound.get_trait_path() { let ty = Type::Path { path }; - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, - &mut ty_generics, + &mut type_bounds, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); + } + if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) { + res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None }); + } else { + let idx = -isize::try_from(rgen.len() + 1).unwrap(); + rgen.insert(SimplifiedParam::Symbol(arg_s), (idx, type_bounds)); + res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None }); } } else if let Type::ImplTrait(ref bounds) = *arg { - let mut ty_generics = Vec::new(); + let mut type_bounds = Vec::new(); for bound in bounds { if let Some(path) = bound.get_trait_path() { let ty = Type::Path { path }; - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, - &mut ty_generics, + &mut type_bounds, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); + if is_return && !type_bounds.is_empty() { + // In parameter position, `impl Trait` is a unique thing. + res.push(RenderType { id: None, generics: Some(type_bounds) }); + } else { + // In parameter position, `impl Trait` is the same as an unnamed generic parameter. + let idx = -isize::try_from(rgen.len() + 1).unwrap(); + rgen.insert(SimplifiedParam::Anonymous(idx), (idx, type_bounds)); + res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None }); + } } else if let Type::Slice(ref ty) = *arg { let mut ty_generics = Vec::new(); - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, &mut ty_generics, + rgen, + is_return, cache, ); - insert_ty(res, arg.clone(), ty_generics); + res.push(get_index_type(arg, ty_generics)); } else if let Type::Array(ref ty, _) = *arg { let mut ty_generics = Vec::new(); - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, &mut ty_generics, + rgen, + is_return, cache, ); - insert_ty(res, arg.clone(), ty_generics); + res.push(get_index_type(arg, ty_generics)); + } else if let Type::Tuple(ref tys) = *arg { + let mut ty_generics = Vec::new(); + for ty in tys { + simplify_fn_type( + self_, + generics, + &ty, + tcx, + recurse + 1, + &mut ty_generics, + rgen, + is_return, + cache, + ); + } + res.push(get_index_type(arg, ty_generics)); } else { // This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't. @@ -594,18 +641,26 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>( let mut ty_generics = Vec::new(); if let Some(arg_generics) = arg.generics() { for gen in arg_generics.iter() { - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, gen, tcx, recurse + 1, &mut ty_generics, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); + let id = get_index_type_id(&arg); + if id.is_some() || !ty_generics.is_empty() { + res.push(RenderType { + id, + generics: if ty_generics.is_empty() { None } else { Some(ty_generics) }, + }); + } } } @@ -618,7 +673,7 @@ fn get_fn_inputs_and_outputs<'tcx>( tcx: TyCtxt<'tcx>, impl_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, -) -> (Vec<RenderType>, Vec<RenderType>) { +) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) { let decl = &func.decl; let combined_generics; @@ -644,21 +699,27 @@ fn get_fn_inputs_and_outputs<'tcx>( (None, &func.generics) }; - let mut all_types = Vec::new(); + let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default(); + + let mut arg_types = Vec::new(); for arg in decl.inputs.values.iter() { - let mut args = Vec::new(); - add_generics_and_bounds_as_types(self_, generics, &arg.type_, tcx, 0, &mut args, cache); - if !args.is_empty() { - all_types.extend(args); - } else { - all_types.push(get_index_type(&arg.type_, vec![])); - } + simplify_fn_type( + self_, + generics, + &arg.type_, + tcx, + 0, + &mut arg_types, + &mut rgen, + false, + cache, + ); } let mut ret_types = Vec::new(); - add_generics_and_bounds_as_types(self_, generics, &decl.output, tcx, 0, &mut ret_types, cache); - if ret_types.is_empty() { - ret_types.push(get_index_type(&decl.output, vec![])); - } - (all_types, ret_types) + simplify_fn_type(self_, generics, &decl.output, tcx, 0, &mut ret_types, &mut rgen, true, cache); + + let mut simplified_params = rgen.into_values().collect::<Vec<_>>(); + simplified_params.sort_by_key(|(idx, _)| -idx); + (arg_types, ret_types, simplified_params.into_iter().map(|(_idx, traits)| traits).collect()) } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index f3da61056..76f63c6f6 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -82,7 +82,7 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf clean::PrimitiveItem(_) => sidebar_primitive(cx, it), clean::UnionItem(ref u) => sidebar_union(cx, it, u), clean::EnumItem(ref e) => sidebar_enum(cx, it, e), - clean::TypedefItem(_) => sidebar_typedef(cx, it), + clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t), clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)], clean::ForeignTypeItem => sidebar_foreign_type(cx, it), _ => vec![], @@ -100,7 +100,7 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf || it.is_union() || it.is_enum() || it.is_mod() - || it.is_typedef() + || it.is_type_alias() { ( match *it.kind { @@ -230,8 +230,33 @@ fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBl } } -fn sidebar_typedef<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> { +fn sidebar_type_alias<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + t: &'a clean::TypeAlias, +) -> 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"))); + match inner_type { + clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive: _ } => { + let mut variants = variants + .iter() + .filter(|i| !i.is_stripped()) + .filter_map(|v| v.name) + .map(|name| Link::new(format!("variant.{name}"), name.to_string())) + .collect::<Vec<_>>(); + variants.sort_unstable(); + + items.push(LinkBlock::new(Link::new("variants", "Variants"), 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)); + } + } + } sidebar_assoc_items(cx, it, &mut items); items } @@ -254,11 +279,12 @@ fn sidebar_assoc_items<'a>( links: &mut Vec<LinkBlock<'a>>, ) { let did = it.item_id.expect_def_id(); - let cache = cx.cache(); + let v = cx.shared.all_impls_for_item(it, it.item_id.expect_def_id()); + let v = v.as_slice(); let mut assoc_consts = Vec::new(); let mut methods = Vec::new(); - if let Some(v) = cache.impls.get(&did) { + if !v.is_empty() { let mut used_links = FxHashSet::default(); let mut id_map = IdMap::new(); @@ -294,7 +320,7 @@ fn sidebar_assoc_items<'a>( cx, &mut deref_methods, impl_, - v, + v.iter().copied(), &mut derefs, &mut used_links, ); @@ -324,7 +350,7 @@ fn sidebar_deref_methods<'a>( cx: &'a Context<'_>, out: &mut Vec<LinkBlock<'a>>, impl_: &Impl, - v: &[Impl], + v: impl Iterator<Item = &'a Impl>, derefs: &mut DefIdSet, used_links: &mut FxHashSet<String>, ) { @@ -334,7 +360,7 @@ fn sidebar_deref_methods<'a>( if let Some((target, real_target)) = impl_.inner_impl().items.iter().find_map(|item| match *item.kind { clean::AssocTypeItem(box ref t, _) => Some(match *t { - clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), + clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_), _ => (&t.type_, &t.type_), }), _ => None, @@ -349,7 +375,7 @@ fn sidebar_deref_methods<'a>( // Avoid infinite cycles return; } - let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + let deref_mut = { v }.any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); let inner_impl = target .def_id(c) .or_else(|| { @@ -400,7 +426,7 @@ fn sidebar_deref_methods<'a>( cx, out, target_deref_impl, - target_impls, + target_impls.iter(), derefs, used_links, ); |