| pub(crate) mod encode; |
| |
| use std::collections::hash_map::Entry; |
| use std::collections::{BTreeMap, VecDeque}; |
| |
| use encode::{bitmap_to_string, write_vlqhex_to_string}; |
| use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; |
| use rustc_middle::ty::TyCtxt; |
| use rustc_span::def_id::DefId; |
| use rustc_span::sym; |
| use rustc_span::symbol::{Symbol, kw}; |
| use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer}; |
| use thin_vec::ThinVec; |
| use tracing::instrument; |
| |
| use crate::clean; |
| use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate}; |
| use crate::formats::cache::{Cache, OrphanImplItem}; |
| use crate::formats::item_type::ItemType; |
| use crate::html::format::join_with_double_colon; |
| use crate::html::markdown::short_markdown_summary; |
| use crate::html::render::ordered_json::OrderedJson; |
| use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; |
| |
| /// The serialized search description sharded version |
| /// |
| /// The `index` is a JSON-encoded list of names and other information. |
| /// |
| /// The desc has newlined descriptions, split up by size into 128KiB shards. |
| /// For example, `(4, "foo\nbar\nbaz\nquux")`. |
| /// |
| /// There is no single, optimal size for these shards, because it depends on |
| /// configuration values that we can't predict or control, such as the version |
| /// of HTTP used (HTTP/1.1 would work better with larger files, while HTTP/2 |
| /// and 3 are more agnostic), transport compression (gzip, zstd, etc), whether |
| /// the search query is going to produce a large number of results or a small |
| /// number, the bandwidth delay product of the network... |
| /// |
| /// Gzipping some standard library descriptions to guess what transport |
| /// compression will do, the compressed file sizes can be as small as 4.9KiB |
| /// or as large as 18KiB (ignoring the final 1.9KiB shard of leftovers). |
| /// A "reasonable" range for files is for them to be bigger than 1KiB, |
| /// since that's about the amount of data that can be transferred in a |
| /// single TCP packet, and 64KiB, the maximum amount of data that |
| /// TCP can transfer in a single round trip without extensions. |
| /// |
| /// [1]: https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media |
| /// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept |
| /// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features |
| pub(crate) struct SerializedSearchIndex { |
| pub(crate) index: OrderedJson, |
| pub(crate) desc: Vec<(usize, String)>, |
| } |
| |
| const DESC_INDEX_SHARD_LEN: usize = 128 * 1024; |
| |
| /// Builds the search index from the collected metadata |
| pub(crate) fn build_index<'tcx>( |
| krate: &clean::Crate, |
| cache: &mut Cache, |
| tcx: TyCtxt<'tcx>, |
| ) -> SerializedSearchIndex { |
| // Maps from ID to position in the `crate_paths` array. |
| let mut itemid_to_pathid = FxHashMap::default(); |
| let mut primitives = FxHashMap::default(); |
| let mut associated_types = FxHashMap::default(); |
| |
| // item type, display path, re-exported internal path |
| let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)> = vec![]; |
| |
| // Attach all orphan items to the type's definition if the type |
| // has since been learned. |
| for &OrphanImplItem { impl_id, parent, ref item, ref impl_generics } in &cache.orphan_impl_items |
| { |
| if let Some((fqp, _)) = cache.paths.get(&parent) { |
| let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache)); |
| cache.search_index.push(IndexItem { |
| ty: item.type_(), |
| defid: item.item_id.as_def_id(), |
| name: item.name.unwrap(), |
| path: join_with_double_colon(&fqp[..fqp.len() - 1]), |
| desc, |
| parent: Some(parent), |
| parent_idx: None, |
| exact_path: None, |
| impl_id, |
| search_type: get_function_type_for_search( |
| item, |
| tcx, |
| impl_generics.as_ref(), |
| Some(parent), |
| cache, |
| ), |
| aliases: item.attrs.get_doc_aliases(), |
| deprecation: item.deprecation(tcx), |
| }); |
| } |
| } |
| |
| let crate_doc = |
| short_markdown_summary(&krate.module.doc_value(), &krate.module.link_names(cache)); |
| |
| // Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias, |
| // we need the alias element to have an array of items. |
| let mut aliases: BTreeMap<String, Vec<usize>> = BTreeMap::new(); |
| |
| // Sort search index items. This improves the compressibility of the search index. |
| cache.search_index.sort_unstable_by(|k1, k2| { |
| // `sort_unstable_by_key` produces lifetime errors |
| // HACK(rustdoc): should not be sorting `CrateNum` or `DefIndex`, this will soon go away, too |
| let k1 = (&k1.path, k1.name.as_str(), &k1.ty, k1.parent.map(|id| (id.index, id.krate))); |
| let k2 = (&k2.path, k2.name.as_str(), &k2.ty, k2.parent.map(|id| (id.index, id.krate))); |
| Ord::cmp(&k1, &k2) |
| }); |
| |
| // Set up alias indexes. |
| for (i, item) in cache.search_index.iter().enumerate() { |
| for alias in &item.aliases[..] { |
| aliases.entry(alias.as_str().to_lowercase()).or_default().push(i); |
| } |
| } |
| |
| // 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 = 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>( |
| map: &mut FxHashMap<F, isize>, |
| itemid: F, |
| lastpathid: &mut isize, |
| crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>, |
| item_type: ItemType, |
| path: &[Symbol], |
| exact_path: Option<&[Symbol]>, |
| ) -> RenderTypeId { |
| match map.entry(itemid) { |
| Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()), |
| Entry::Vacant(entry) => { |
| let pathid = *lastpathid; |
| entry.insert(pathid); |
| *lastpathid += 1; |
| crate_paths.push(( |
| item_type, |
| path.to_vec(), |
| exact_path.map(|path| path.to_vec()), |
| )); |
| RenderTypeId::Index(pathid) |
| } |
| } |
| } |
| |
| fn convert_render_type_id( |
| id: RenderTypeId, |
| cache: &mut Cache, |
| itemid_to_pathid: &mut FxHashMap<ItemId, isize>, |
| primitives: &mut FxHashMap<Symbol, isize>, |
| associated_types: &mut FxHashMap<Symbol, isize>, |
| lastpathid: &mut isize, |
| crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>, |
| ) -> Option<RenderTypeId> { |
| let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache; |
| match id { |
| RenderTypeId::Mut => Some(insert_into_map( |
| primitives, |
| kw::Mut, |
| lastpathid, |
| crate_paths, |
| ItemType::Keyword, |
| &[kw::Mut], |
| None, |
| )), |
| RenderTypeId::DefId(defid) => { |
| if let Some(&(ref fqp, item_type)) = |
| paths.get(&defid).or_else(|| external_paths.get(&defid)) |
| { |
| let exact_fqp = exact_paths |
| .get(&defid) |
| .or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp)) |
| // Re-exports only count if the name is exactly the same. |
| // This is a size optimization, since it means we only need |
| // to store the name once (and the path is re-used for everything |
| // exported from this same module). It's also likely to Do |
| // What I Mean, since if a re-export changes the name, it might |
| // also be a change in semantic meaning. |
| .filter(|fqp| fqp.last() == fqp.last()); |
| Some(insert_into_map( |
| itemid_to_pathid, |
| ItemId::DefId(defid), |
| lastpathid, |
| crate_paths, |
| item_type, |
| fqp, |
| exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp), |
| )) |
| } else { |
| None |
| } |
| } |
| RenderTypeId::Primitive(primitive) => { |
| let sym = primitive.as_sym(); |
| Some(insert_into_map( |
| primitives, |
| sym, |
| lastpathid, |
| crate_paths, |
| ItemType::Primitive, |
| &[sym], |
| None, |
| )) |
| } |
| RenderTypeId::Index(_) => Some(id), |
| RenderTypeId::AssociatedType(sym) => Some(insert_into_map( |
| associated_types, |
| sym, |
| lastpathid, |
| crate_paths, |
| ItemType::AssocType, |
| &[sym], |
| None, |
| )), |
| } |
| } |
| |
| fn convert_render_type( |
| ty: &mut RenderType, |
| cache: &mut Cache, |
| itemid_to_pathid: &mut FxHashMap<ItemId, isize>, |
| primitives: &mut FxHashMap<Symbol, isize>, |
| associated_types: &mut FxHashMap<Symbol, isize>, |
| lastpathid: &mut isize, |
| crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>, |
| ) { |
| if let Some(generics) = &mut ty.generics { |
| for item in generics { |
| convert_render_type( |
| item, |
| cache, |
| itemid_to_pathid, |
| primitives, |
| associated_types, |
| lastpathid, |
| crate_paths, |
| ); |
| } |
| } |
| if let Some(bindings) = &mut ty.bindings { |
| bindings.retain_mut(|(associated_type, constraints)| { |
| let converted_associated_type = convert_render_type_id( |
| *associated_type, |
| cache, |
| itemid_to_pathid, |
| primitives, |
| associated_types, |
| lastpathid, |
| crate_paths, |
| ); |
| let Some(converted_associated_type) = converted_associated_type else { |
| return false; |
| }; |
| *associated_type = converted_associated_type; |
| for constraint in constraints { |
| convert_render_type( |
| constraint, |
| cache, |
| itemid_to_pathid, |
| primitives, |
| associated_types, |
| lastpathid, |
| crate_paths, |
| ); |
| } |
| true |
| }); |
| } |
| let Some(id) = ty.id.clone() else { |
| assert!(ty.generics.is_some()); |
| return; |
| }; |
| ty.id = convert_render_type_id( |
| id, |
| cache, |
| itemid_to_pathid, |
| primitives, |
| associated_types, |
| lastpathid, |
| crate_paths, |
| ); |
| } |
| if let Some(search_type) = &mut item.search_type { |
| for item in &mut search_type.inputs { |
| convert_render_type( |
| item, |
| cache, |
| &mut itemid_to_pathid, |
| &mut primitives, |
| &mut associated_types, |
| &mut lastpathid, |
| &mut crate_paths, |
| ); |
| } |
| for item in &mut search_type.output { |
| convert_render_type( |
| item, |
| cache, |
| &mut itemid_to_pathid, |
| &mut primitives, |
| &mut associated_types, |
| &mut lastpathid, |
| &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 associated_types, |
| &mut lastpathid, |
| &mut crate_paths, |
| ); |
| } |
| } |
| } |
| } |
| |
| let Cache { ref paths, ref exact_paths, ref external_paths, .. } = *cache; |
| |
| // Then, on parent modules |
| let crate_items: Vec<&IndexItem> = search_index |
| .iter_mut() |
| .map(|item| { |
| item.parent_idx = |
| item.parent.and_then(|defid| match itemid_to_pathid.entry(ItemId::DefId(defid)) { |
| Entry::Occupied(entry) => Some(*entry.get()), |
| Entry::Vacant(entry) => { |
| let pathid = lastpathid; |
| entry.insert(pathid); |
| lastpathid += 1; |
| |
| if let Some(&(ref fqp, short)) = paths.get(&defid) { |
| let exact_fqp = exact_paths |
| .get(&defid) |
| .or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp)) |
| .filter(|exact_fqp| { |
| exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp |
| }); |
| crate_paths.push((short, fqp.clone(), exact_fqp.cloned())); |
| Some(pathid) |
| } else { |
| None |
| } |
| } |
| }); |
| |
| if let Some(defid) = item.defid |
| && item.parent_idx.is_none() |
| { |
| // If this is a re-export, retain the original path. |
| // Associated items don't use this. |
| // Their parent carries the exact fqp instead. |
| let exact_fqp = exact_paths |
| .get(&defid) |
| .or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp)); |
| item.exact_path = exact_fqp.and_then(|fqp| { |
| // Re-exports only count if the name is exactly the same. |
| // This is a size optimization, since it means we only need |
| // to store the name once (and the path is re-used for everything |
| // exported from this same module). It's also likely to Do |
| // What I Mean, since if a re-export changes the name, it might |
| // also be a change in semantic meaning. |
| if fqp.last() != Some(&item.name) { |
| return None; |
| } |
| let path = |
| if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) { |
| // `#[macro_export]` always exports to the crate root. |
| tcx.crate_name(defid.krate).to_string() |
| } else { |
| if fqp.len() < 2 { |
| return None; |
| } |
| join_with_double_colon(&fqp[..fqp.len() - 1]) |
| }; |
| if path == item.path { |
| return None; |
| } |
| Some(path) |
| }); |
| } |
| |
| // Omit the parent path if it is same to that of the prior item. |
| if lastpath == &item.path { |
| item.path.clear(); |
| } else { |
| lastpath = &item.path; |
| } |
| |
| &*item |
| }) |
| .collect(); |
| |
| // Find associated items that need disambiguators |
| let mut associated_item_duplicates = FxHashMap::<(isize, ItemType, Symbol), usize>::default(); |
| |
| for &item in &crate_items { |
| if item.impl_id.is_some() |
| && let Some(parent_idx) = item.parent_idx |
| { |
| let count = |
| associated_item_duplicates.entry((parent_idx, item.ty, item.name)).or_insert(0); |
| *count += 1; |
| } |
| } |
| |
| let associated_item_disambiguators = crate_items |
| .iter() |
| .enumerate() |
| .filter_map(|(index, item)| { |
| let impl_id = ItemId::DefId(item.impl_id?); |
| let parent_idx = item.parent_idx?; |
| let count = *associated_item_duplicates.get(&(parent_idx, item.ty, item.name))?; |
| if count > 1 { Some((index, render::get_id_for_impl(tcx, impl_id))) } else { None } |
| }) |
| .collect::<Vec<_>>(); |
| |
| struct CrateData<'a> { |
| items: Vec<&'a IndexItem>, |
| paths: Vec<(ItemType, Vec<Symbol>, Option<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>>, |
| // Used when a type has more than one impl with an associated item with the same name. |
| associated_item_disambiguators: &'a Vec<(usize, String)>, |
| // A list of shard lengths encoded as vlqhex. See the comment in write_vlqhex_to_string |
| // for information on the format. |
| desc_index: String, |
| // A list of items with no description. This is eventually turned into a bitmap. |
| empty_desc: Vec<u32>, |
| } |
| |
| struct Paths { |
| ty: ItemType, |
| name: Symbol, |
| path: Option<usize>, |
| exact_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)?; |
| } |
| if let Some(ref path) = self.exact_path { |
| assert!(self.path.is_some()); |
| 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, exact) in &self.paths { |
| if path.len() < 2 { |
| paths.push(Paths { ty: *ty, name: path[0], path: None, exact_path: None }); |
| continue; |
| } |
| let full_path = join_with_double_colon(&path[..path.len() - 1]); |
| let full_exact_path = exact |
| .as_ref() |
| .filter(|exact| exact.last() == path.last() && exact.len() >= 2) |
| .map(|exact| join_with_double_colon(&exact[..exact.len() - 1])); |
| let exact_path = extra_paths.len() + self.items.len(); |
| let exact_path = full_exact_path.as_ref().map(|full_exact_path| match extra_paths |
| .entry(full_exact_path.clone()) |
| { |
| Entry::Occupied(entry) => *entry.get(), |
| Entry::Vacant(entry) => { |
| if let Some(index) = mod_paths.get(&full_exact_path) { |
| return *index; |
| } |
| entry.insert(exact_path); |
| if !revert_extra_paths.contains_key(&exact_path) { |
| revert_extra_paths.insert(exact_path, full_exact_path.clone()); |
| } |
| exact_path |
| } |
| }); |
| if let Some(index) = mod_paths.get(&full_path) { |
| paths.push(Paths { |
| ty: *ty, |
| name: *path.last().unwrap(), |
| path: Some(*index), |
| exact_path, |
| }); |
| 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(); |
| match extra_paths.entry(full_path.clone()) { |
| Entry::Occupied(entry) => { |
| paths.push(Paths { |
| ty: *ty, |
| name: *path.last().unwrap(), |
| path: Some(*entry.get()), |
| exact_path, |
| }); |
| } |
| Entry::Vacant(entry) => { |
| entry.insert(index); |
| if !revert_extra_paths.contains_key(&index) { |
| revert_extra_paths.insert(index, full_path); |
| } |
| paths.push(Paths { |
| ty: *ty, |
| name: *path.last().unwrap(), |
| path: Some(index), |
| exact_path, |
| }); |
| } |
| } |
| } |
| |
| // Direct exports use adjacent arrays for the current crate's items, |
| // but re-exported exact paths don't. |
| let mut re_exports = Vec::new(); |
| for (item_index, item) in self.items.iter().enumerate() { |
| if let Some(exact_path) = item.exact_path.as_ref() { |
| if let Some(path_index) = mod_paths.get(&exact_path) { |
| re_exports.push((item_index, *path_index)); |
| } else { |
| let path_index = extra_paths.len() + self.items.len(); |
| let path_index = match extra_paths.entry(exact_path.clone()) { |
| Entry::Occupied(entry) => *entry.get(), |
| Entry::Vacant(entry) => { |
| entry.insert(path_index); |
| if !revert_extra_paths.contains_key(&path_index) { |
| revert_extra_paths.insert(path_index, exact_path.clone()); |
| } |
| path_index |
| } |
| }; |
| re_exports.push((item_index, path_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 parents = String::with_capacity(self.items.len()); |
| let mut parents_backref_queue = VecDeque::new(); |
| let mut functions = String::with_capacity(self.items.len()); |
| let mut deprecated = Vec::with_capacity(self.items.len()); |
| |
| let mut type_backref_queue = VecDeque::new(); |
| |
| let mut last_name = None; |
| 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 |
| ); |
| assert!( |
| parents_backref_queue.len() <= 16, |
| "the string encoding only supports 16 slots of lookback" |
| ); |
| let parent: i32 = item.parent_idx.map(|x| x + 1).unwrap_or(0).try_into().unwrap(); |
| if let Some(idx) = parents_backref_queue.iter().position(|p: &i32| *p == parent) { |
| parents.push( |
| char::try_from('0' as u32 + u32::try_from(idx).unwrap()) |
| .expect("last possible value is '?'"), |
| ); |
| } else if parent == 0 { |
| write_vlqhex_to_string(parent, &mut parents); |
| } else { |
| parents_backref_queue.push_front(parent); |
| write_vlqhex_to_string(parent, &mut parents); |
| if parents_backref_queue.len() > 16 { |
| parents_backref_queue.pop_back(); |
| } |
| } |
| |
| if Some(item.name.as_str()) == last_name { |
| names.push(""); |
| } else { |
| names.push(item.name.as_str()); |
| last_name = Some(item.name.as_str()); |
| } |
| |
| if !item.path.is_empty() { |
| full_paths.push((index, &item.path)); |
| } |
| |
| match &item.search_type { |
| Some(ty) => ty.write_to_string(&mut functions, &mut type_backref_queue), |
| None => functions.push('`'), |
| } |
| |
| if item.deprecation.is_some() { |
| // bitmasks always use 1-indexing for items, with 0 as the crate itself |
| deprecated.push(u32::try_from(index + 1).unwrap()); |
| } |
| } |
| |
| 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("t", &types)?; |
| crate_data.serialize_field("n", &names)?; |
| crate_data.serialize_field("q", &full_paths)?; |
| crate_data.serialize_field("i", &parents)?; |
| crate_data.serialize_field("f", &functions)?; |
| crate_data.serialize_field("D", &self.desc_index)?; |
| crate_data.serialize_field("p", &paths)?; |
| crate_data.serialize_field("r", &re_exports)?; |
| crate_data.serialize_field("b", &self.associated_item_disambiguators)?; |
| crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?; |
| crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?; |
| if has_aliases { |
| crate_data.serialize_field("a", &self.aliases)?; |
| } |
| crate_data.end() |
| } |
| } |
| |
| let (empty_desc, desc) = { |
| let mut empty_desc = Vec::new(); |
| let mut result = Vec::new(); |
| let mut set = String::new(); |
| let mut len: usize = 0; |
| let mut item_index: u32 = 0; |
| for desc in std::iter::once(&crate_doc).chain(crate_items.iter().map(|item| &item.desc)) { |
| if desc == "" { |
| empty_desc.push(item_index); |
| item_index += 1; |
| continue; |
| } |
| if set.len() >= DESC_INDEX_SHARD_LEN { |
| result.push((len, std::mem::replace(&mut set, String::new()))); |
| len = 0; |
| } else if len != 0 { |
| set.push('\n'); |
| } |
| set.push_str(&desc); |
| len += 1; |
| item_index += 1; |
| } |
| result.push((len, std::mem::replace(&mut set, String::new()))); |
| (empty_desc, result) |
| }; |
| |
| let desc_index = { |
| let mut desc_index = String::with_capacity(desc.len() * 4); |
| for &(len, _) in desc.iter() { |
| write_vlqhex_to_string(len.try_into().unwrap(), &mut desc_index); |
| } |
| desc_index |
| }; |
| |
| assert_eq!( |
| crate_items.len() + 1, |
| desc.iter().map(|(len, _)| *len).sum::<usize>() + empty_desc.len() |
| ); |
| |
| // The index, which is actually used to search, is JSON |
| // It uses `JSON.parse(..)` to actually load, since JSON |
| // parses faster than the full JavaScript syntax. |
| let crate_name = krate.name(tcx); |
| let data = CrateData { |
| items: crate_items, |
| paths: crate_paths, |
| aliases: &aliases, |
| associated_item_disambiguators: &associated_item_disambiguators, |
| desc_index, |
| empty_desc, |
| }; |
| let index = OrderedJson::array_unsorted([ |
| OrderedJson::serialize(crate_name.as_str()).unwrap(), |
| OrderedJson::serialize(data).unwrap(), |
| ]); |
| SerializedSearchIndex { index, desc } |
| } |
| |
| pub(crate) fn get_function_type_for_search<'tcx>( |
| item: &clean::Item, |
| tcx: TyCtxt<'tcx>, |
| impl_generics: Option<&(clean::Type, clean::Generics)>, |
| parent: Option<DefId>, |
| cache: &Cache, |
| ) -> Option<IndexItemFunctionType> { |
| let mut trait_info = None; |
| let impl_or_trait_generics = impl_generics.or_else(|| { |
| if let Some(def_id) = parent |
| && let Some(trait_) = cache.traits.get(&def_id) |
| && let Some((path, _)) = |
| cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id)) |
| { |
| let path = clean::Path { |
| res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id), |
| segments: path |
| .iter() |
| .map(|name| clean::PathSegment { |
| name: *name, |
| args: clean::GenericArgs::AngleBracketed { |
| args: Vec::new().into_boxed_slice(), |
| constraints: ThinVec::new(), |
| }, |
| }) |
| .collect(), |
| }; |
| trait_info = Some((clean::Type::Path { path }, trait_.generics.clone())); |
| Some(trait_info.as_ref().unwrap()) |
| } else { |
| None |
| } |
| }); |
| let (mut inputs, mut output, where_clause) = match item.kind { |
| clean::ForeignFunctionItem(ref f, _) |
| | clean::FunctionItem(ref f) |
| | clean::MethodItem(ref f, _) |
| | clean::TyMethodItem(ref f) => { |
| get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache) |
| } |
| _ => return None, |
| }; |
| |
| 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, where_clause }) |
| } |
| |
| fn get_index_type( |
| clean_type: &clean::Type, |
| generics: Vec<RenderType>, |
| rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, |
| ) -> RenderType { |
| RenderType { |
| id: get_index_type_id(clean_type, rgen), |
| generics: if generics.is_empty() { None } else { Some(generics) }, |
| bindings: None, |
| } |
| } |
| |
| fn get_index_type_id( |
| clean_type: &clean::Type, |
| rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, |
| ) -> Option<RenderTypeId> { |
| use rustc_hir::def::{DefKind, Res}; |
| match *clean_type { |
| clean::Type::Path { ref path, .. } => Some(RenderTypeId::DefId(path.def_id())), |
| clean::DynTrait(ref bounds, _) => { |
| bounds.get(0).map(|b| RenderTypeId::DefId(b.trait_.def_id())) |
| } |
| clean::Primitive(p) => Some(RenderTypeId::Primitive(p)), |
| clean::BorrowedRef { .. } => Some(RenderTypeId::Primitive(clean::PrimitiveType::Reference)), |
| clean::RawPointer(_, ref type_) => get_index_type_id(type_, rgen), |
| // 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::BareFunction(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Fn)), |
| clean::Tuple(ref n) if n.is_empty() => { |
| Some(RenderTypeId::Primitive(clean::PrimitiveType::Unit)) |
| } |
| clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)), |
| clean::QPath(ref data) => { |
| if data.self_type.is_self_type() |
| && let Some(clean::Path { res: Res::Def(DefKind::Trait, trait_), .. }) = data.trait_ |
| { |
| let idx = -isize::try_from(rgen.len() + 1).unwrap(); |
| let (idx, _) = rgen |
| .entry(SimplifiedParam::AssociatedType(trait_, data.assoc.name)) |
| .or_insert_with(|| (idx, Vec::new())); |
| Some(RenderTypeId::Index(*idx)) |
| } else { |
| None |
| } |
| } |
| // Not supported yet |
| clean::Type::Pat(..) |
| | clean::Generic(_) |
| | clean::SelfTy |
| | clean::ImplTrait(_) |
| | clean::Infer => None, |
| } |
| } |
| |
| #[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), |
| // in a trait definition, the associated types are all bound to |
| // their own type parameter |
| AssociatedType(DefId, Symbol), |
| } |
| |
| /// The point of this function is to lower generics and types into the simplified form that the |
| /// frontend search engine can use. |
| /// |
| /// 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. |
| /// |
| /// This function also works recursively. |
| #[instrument(level = "trace", skip(tcx, res, rgen, cache))] |
| fn simplify_fn_type<'a, 'tcx>( |
| self_: Option<&'a Type>, |
| generics: &Generics, |
| arg: &'a Type, |
| tcx: TyCtxt<'tcx>, |
| recurse: usize, |
| res: &mut Vec<RenderType>, |
| rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, |
| is_return: bool, |
| cache: &Cache, |
| ) { |
| if recurse >= 10 { |
| // FIXME: remove this whole recurse thing when the recursion bug is fixed |
| // See #59502 for the original issue. |
| return; |
| } |
| |
| // First, check if it's "Self". |
| let (is_self, arg) = if let Some(self_) = self_ |
| && arg.is_self_type() |
| { |
| (true, self_) |
| } else { |
| (false, arg) |
| }; |
| |
| // If this argument is a type parameter and not a trait bound or a type, we need to look |
| // for its bounds. |
| match *arg { |
| Type::Generic(arg_s) => { |
| // 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, .. } => *ty == *arg, |
| _ => false, |
| }) { |
| 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 }; |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut type_bounds, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| } |
| } |
| // 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) { |
| for bound in bound.get_bounds().unwrap_or(&[]) { |
| if let Some(path) = bound.get_trait_path() { |
| let ty = Type::Path { path }; |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut type_bounds, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| } |
| } |
| if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) { |
| res.push(RenderType { |
| id: Some(RenderTypeId::Index(*idx)), |
| generics: None, |
| bindings: 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, |
| bindings: None, |
| }); |
| } |
| } |
| Type::ImplTrait(ref bounds) => { |
| let mut type_bounds = Vec::new(); |
| for bound in bounds { |
| if let Some(path) = bound.get_trait_path() { |
| let ty = Type::Path { path }; |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut type_bounds, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| } |
| if is_return && !type_bounds.is_empty() { |
| // In return position, `impl Trait` is a unique thing. |
| res.push(RenderType { id: None, generics: Some(type_bounds), bindings: None }); |
| } 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, |
| bindings: None, |
| }); |
| } |
| } |
| Type::Slice(ref ty) => { |
| let mut ty_generics = Vec::new(); |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut ty_generics, |
| rgen, |
| is_return, |
| cache, |
| ); |
| res.push(get_index_type(arg, ty_generics, rgen)); |
| } |
| Type::Array(ref ty, _) => { |
| let mut ty_generics = Vec::new(); |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut ty_generics, |
| rgen, |
| is_return, |
| cache, |
| ); |
| res.push(get_index_type(arg, ty_generics, rgen)); |
| } |
| Type::Tuple(ref tys) => { |
| 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, rgen)); |
| } |
| Type::BareFunction(ref bf) => { |
| let mut ty_generics = Vec::new(); |
| for ty in bf.decl.inputs.values.iter().map(|arg| &arg.type_) { |
| simplify_fn_type( |
| self_, |
| generics, |
| ty, |
| tcx, |
| recurse + 1, |
| &mut ty_generics, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| // The search index, for simplicity's sake, represents fn pointers and closures |
| // the same way: as a tuple for the parameters, and an associated type for the |
| // return type. |
| let mut ty_output = Vec::new(); |
| simplify_fn_type( |
| self_, |
| generics, |
| &bf.decl.output, |
| tcx, |
| recurse + 1, |
| &mut ty_output, |
| rgen, |
| is_return, |
| cache, |
| ); |
| let ty_bindings = vec![(RenderTypeId::AssociatedType(sym::Output), ty_output)]; |
| res.push(RenderType { |
| id: get_index_type_id(&arg, rgen), |
| bindings: Some(ty_bindings), |
| generics: Some(ty_generics), |
| }); |
| } |
| Type::BorrowedRef { lifetime: _, mutability, ref type_ } => { |
| let mut ty_generics = Vec::new(); |
| if mutability.is_mut() { |
| ty_generics.push(RenderType { |
| id: Some(RenderTypeId::Mut), |
| generics: None, |
| bindings: None, |
| }); |
| } |
| simplify_fn_type( |
| self_, |
| generics, |
| &type_, |
| tcx, |
| recurse + 1, |
| &mut ty_generics, |
| rgen, |
| is_return, |
| cache, |
| ); |
| res.push(get_index_type(arg, ty_generics, rgen)); |
| } |
| _ => { |
| // 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. |
| // |
| // So in here, we can add it directly and look for its own type parameters (so for `Option`, |
| // we will look for them but not for `T`). |
| let mut ty_generics = Vec::new(); |
| let mut ty_constraints = Vec::new(); |
| if let Some(arg_generics) = arg.generic_args() { |
| for ty in arg_generics.into_iter().filter_map(|param| match param { |
| clean::GenericArg::Type(ty) => Some(ty), |
| _ => None, |
| }) { |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut ty_generics, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| for constraint in arg_generics.constraints() { |
| simplify_fn_constraint( |
| self_, |
| generics, |
| &constraint, |
| tcx, |
| recurse + 1, |
| &mut ty_constraints, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| } |
| // Every trait associated type on self gets assigned to a type parameter index |
| // this same one is used later for any appearances of these types |
| // |
| // for example, Iterator::next is: |
| // |
| // trait Iterator { |
| // fn next(&mut self) -> Option<Self::Item> |
| // } |
| // |
| // Self is technically just Iterator, but we want to pretend it's more like this: |
| // |
| // fn next<T>(self: Iterator<Item=T>) -> Option<T> |
| if is_self |
| && let Type::Path { path } = arg |
| && let def_id = path.def_id() |
| && let Some(trait_) = cache.traits.get(&def_id) |
| && trait_.items.iter().any(|at| at.is_ty_associated_type()) |
| { |
| for assoc_ty in &trait_.items { |
| if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &assoc_ty.kind |
| && let Some(name) = assoc_ty.name |
| { |
| let idx = -isize::try_from(rgen.len() + 1).unwrap(); |
| let (idx, stored_bounds) = rgen |
| .entry(SimplifiedParam::AssociatedType(def_id, name)) |
| .or_insert_with(|| (idx, Vec::new())); |
| let idx = *idx; |
| if stored_bounds.is_empty() { |
| // Can't just pass stored_bounds to simplify_fn_type, |
| // because it also accepts rgen as a parameter. |
| // Instead, have it fill in this local, then copy it into the map afterward. |
| let mut type_bounds = Vec::new(); |
| for bound in bounds { |
| if let Some(path) = bound.get_trait_path() { |
| let ty = Type::Path { path }; |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut type_bounds, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| } |
| let stored_bounds = &mut rgen |
| .get_mut(&SimplifiedParam::AssociatedType(def_id, name)) |
| .unwrap() |
| .1; |
| if stored_bounds.is_empty() { |
| *stored_bounds = type_bounds; |
| } |
| } |
| ty_constraints.push((RenderTypeId::AssociatedType(name), vec![ |
| RenderType { |
| id: Some(RenderTypeId::Index(idx)), |
| generics: None, |
| bindings: None, |
| }, |
| ])) |
| } |
| } |
| } |
| let id = get_index_type_id(&arg, rgen); |
| if id.is_some() || !ty_generics.is_empty() { |
| res.push(RenderType { |
| id, |
| bindings: if ty_constraints.is_empty() { None } else { Some(ty_constraints) }, |
| generics: if ty_generics.is_empty() { None } else { Some(ty_generics) }, |
| }); |
| } |
| } |
| } |
| } |
| |
| fn simplify_fn_constraint<'a, 'tcx>( |
| self_: Option<&'a Type>, |
| generics: &Generics, |
| constraint: &'a clean::AssocItemConstraint, |
| tcx: TyCtxt<'tcx>, |
| recurse: usize, |
| res: &mut Vec<(RenderTypeId, Vec<RenderType>)>, |
| rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, |
| is_return: bool, |
| cache: &Cache, |
| ) { |
| let mut ty_constraints = Vec::new(); |
| let ty_constrained_assoc = RenderTypeId::AssociatedType(constraint.assoc.name); |
| for param in &constraint.assoc.args { |
| match param { |
| clean::GenericArg::Type(arg) => simplify_fn_type( |
| self_, |
| generics, |
| &arg, |
| tcx, |
| recurse + 1, |
| &mut ty_constraints, |
| rgen, |
| is_return, |
| cache, |
| ), |
| clean::GenericArg::Lifetime(_) |
| | clean::GenericArg::Const(_) |
| | clean::GenericArg::Infer => {} |
| } |
| } |
| for constraint in constraint.assoc.args.constraints() { |
| simplify_fn_constraint( |
| self_, |
| generics, |
| &constraint, |
| tcx, |
| recurse + 1, |
| res, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| match &constraint.kind { |
| clean::AssocItemConstraintKind::Equality { term } => { |
| if let clean::Term::Type(arg) = &term { |
| simplify_fn_type( |
| self_, |
| generics, |
| arg, |
| tcx, |
| recurse + 1, |
| &mut ty_constraints, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| } |
| clean::AssocItemConstraintKind::Bound { bounds } => { |
| for bound in &bounds[..] { |
| if let Some(path) = bound.get_trait_path() { |
| let ty = Type::Path { path }; |
| simplify_fn_type( |
| self_, |
| generics, |
| &ty, |
| tcx, |
| recurse + 1, |
| &mut ty_constraints, |
| rgen, |
| is_return, |
| cache, |
| ); |
| } |
| } |
| } |
| } |
| res.push((ty_constrained_assoc, ty_constraints)); |
| } |
| |
| /// Return the full list of types when bounds have been resolved. |
| /// |
| /// i.e. `fn foo<A: Display, B: Option<A>>(x: u32, y: B)` will return |
| /// `[u32, Display, Option]`. |
| fn get_fn_inputs_and_outputs<'tcx>( |
| func: &Function, |
| tcx: TyCtxt<'tcx>, |
| impl_or_trait_generics: Option<&(clean::Type, clean::Generics)>, |
| cache: &Cache, |
| ) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) { |
| let decl = &func.decl; |
| |
| let mut rgen: FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default(); |
| |
| let combined_generics; |
| let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_or_trait_generics { |
| match (impl_generics.is_empty(), func.generics.is_empty()) { |
| (true, _) => (Some(impl_self), &func.generics), |
| (_, true) => (Some(impl_self), impl_generics), |
| (false, false) => { |
| let params = |
| func.generics.params.iter().chain(&impl_generics.params).cloned().collect(); |
| let where_predicates = func |
| .generics |
| .where_predicates |
| .iter() |
| .chain(&impl_generics.where_predicates) |
| .cloned() |
| .collect(); |
| combined_generics = clean::Generics { params, where_predicates }; |
| (Some(impl_self), &combined_generics) |
| } |
| } |
| } else { |
| (None, &func.generics) |
| }; |
| |
| let mut arg_types = Vec::new(); |
| for arg in decl.inputs.values.iter() { |
| simplify_fn_type( |
| self_, |
| generics, |
| &arg.type_, |
| tcx, |
| 0, |
| &mut arg_types, |
| &mut rgen, |
| false, |
| cache, |
| ); |
| } |
| |
| let mut ret_types = Vec::new(); |
| 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()) |
| } |