From 539957130d095589bdec370f407e1637b156d537 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 31 Aug 2023 18:51:18 +0200 Subject: [PATCH 1/5] Improve `search.js` code --- src/librustdoc/html/static/js/search.js | 30 +++++++++++-------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 42088e7355440..00b6b3a4420bf 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -263,7 +263,6 @@ function initSearch(rawSearchIndex) { * @returns {integer} */ function buildTypeMapIndex(name) { - if (name === "" || name === null) { return -1; } @@ -1380,7 +1379,7 @@ function initSearch(rawSearchIndex) { * @type Map */ const queryElemSet = new Map(); - const addQueryElemToQueryElemSet = function addQueryElemToQueryElemSet(queryElem) { + const addQueryElemToQueryElemSet = queryElem => { let currentQueryElemList; if (queryElemSet.has(queryElem.id)) { currentQueryElemList = queryElemSet.get(queryElem.id); @@ -1397,7 +1396,7 @@ function initSearch(rawSearchIndex) { * @type Map */ const fnTypeSet = new Map(); - const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) { + const addFnTypeToFnTypeSet = fnType => { // Pure generic, or an item that's not matched by any query elems. // Try [unboxing] it. // @@ -2385,12 +2384,11 @@ ${item.displayPath}${name}\ lowercasePaths ); } + // `0` is used as a sentinel because it's fewer bytes than `null` + const item = pathIndex === 0 ? null : lowercasePaths[pathIndex - 1]; return { - // `0` is used as a sentinel because it's fewer bytes than `null` - id: pathIndex === 0 - ? -1 - : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name), - ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, + id: item === null ? -1 : buildTypeMapIndex(item.name), + ty: item === null ? null : item.ty, generics: generics, }; }); @@ -2419,14 +2417,13 @@ ${item.displayPath}${name}\ if (functionSearchType === 0) { return null; } - let inputs, output; + let inputs, output, item; if (typeof functionSearchType[INPUTS_DATA] === "number") { const pathIndex = functionSearchType[INPUTS_DATA]; + item = pathIndex === 0 ? null : lowercasePaths[pathIndex - 1]; inputs = [{ - id: pathIndex === 0 - ? -1 - : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name), - ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, + id: item === null ? -1 : buildTypeMapIndex(item.name), + ty: item === null ? null : item.ty, generics: [], }]; } else { @@ -2438,11 +2435,10 @@ ${item.displayPath}${name}\ if (functionSearchType.length > 1) { if (typeof functionSearchType[OUTPUT_DATA] === "number") { const pathIndex = functionSearchType[OUTPUT_DATA]; + item = pathIndex === 0 ? null : lowercasePaths[pathIndex - 1]; output = [{ - id: pathIndex === 0 - ? -1 - : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name), - ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, + id: item === null ? -1 : buildTypeMapIndex(item.name), + ty: item === null ? null : item.ty, generics: [], }]; } else { From 09160b3f4583bbd14d62362fd1126a8232f19c8b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 1 Sep 2023 14:30:31 +0200 Subject: [PATCH 2/5] [rustdoc] Fix path in type-based search --- src/librustdoc/html/render/search_index.rs | 63 +++++++++--- src/librustdoc/html/static/js/search.js | 112 ++++++++++++++++----- 2 files changed, 139 insertions(+), 36 deletions(-) diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index f34be120d292b..a7771c31f853b 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use rustc_data_structures::fx::FxHashMap; 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}; @@ -78,9 +78,9 @@ pub(crate) fn build_index<'tcx>( map: &mut FxHashMap, itemid: F, lastpathid: &mut usize, - crate_paths: &mut Vec<(ItemType, Symbol)>, + crate_paths: &mut Vec<(ItemType, Vec)>, 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)); } } @@ -100,7 +100,7 @@ pub(crate) fn build_index<'tcx>( itemid_to_pathid: &mut FxHashMap, primitives: &mut FxHashMap, lastpathid: &mut usize, - crate_paths: &mut Vec<(ItemType, Symbol)>, + crate_paths: &mut Vec<(ItemType, Vec)>, ) { 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(_) => {} @@ -191,7 +191,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,18 +213,58 @@ pub(crate) fn build_index<'tcx>( struct CrateData<'a> { doc: String, items: Vec<&'a IndexItem>, - paths: Vec<(ItemType, Symbol)>, + paths: Vec<(ItemType, Vec)>, // 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>, } + struct Paths { + ty: ItemType, + name: Symbol, + path: Option, + } + + impl Serialize for Paths { + fn serialize(&self, serializer: S) -> Result + 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(&self, serializer: S) -> Result where S: Serializer, { + 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 paths = self + .paths + .iter() + .map(|(ty, path)| { + if path.len() < 2 { + return Paths { ty: *ty, name: path[0], path: None }; + } + let index = mod_paths.get(&join_with_double_colon(&path[..path.len() - 1])); + Paths { ty: *ty, name: *path.last().unwrap(), path: index.copied() } + }) + .collect::>(); + let has_aliases = !self.aliases.is_empty(); let mut crate_data = serializer.serialize_struct("CrateData", if has_aliases { 9 } else { 8 })?; @@ -321,10 +361,7 @@ pub(crate) fn build_index<'tcx>( .filter_map(|(index, item)| item.deprecation.map(|_| index)) .collect::>(), )?; - crate_data.serialize_field( - "p", - &self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::>(), - )?; + crate_data.serialize_field("p", &paths)?; if has_aliases { crate_data.serialize_field("a", &self.aliases)?; } diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 00b6b3a4420bf..449168c460bed 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1462,6 +1462,32 @@ function initSearch(rawSearchIndex) { if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) { continue; } + const queryElemPathLength = queryElem.pathWithoutLast.length; + // If the query element is a path (it contains `::`), we need to check if this + // path is compatible with the target type. + if (queryElemPathLength > 0) { + const fnTypePath = fnType.path !== undefined && fnType.path !== null ? + fnType.path.split("::") : []; + // If the path provided in the query element is longer than this type, + // no need to check it since it won't match in any case. + if (queryElemPathLength > fnTypePath.length) { + continue; + } + let i = 0; + for (const path of fnTypePath) { + if (path === queryElem.pathWithoutLast[i]) { + i += 1; + if (i >= queryElemPathLength) { + break; + } + } + } + if (i < queryElemPathLength) { + // If we didn't find all parts of the path of the query element inside + // the fn type, then it's not the right one. + continue; + } + } if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) { currentFnTypeList.splice(i, 1); const result = doHandleQueryElemList(currentFnTypeList, queryElemList); @@ -1862,14 +1888,14 @@ function initSearch(rawSearchIndex) { * @param {QueryElement} elem */ function convertNameToId(elem) { - if (typeNameIdMap.has(elem.name)) { - elem.id = typeNameIdMap.get(elem.name); + if (typeNameIdMap.has(elem.pathLast)) { + elem.id = typeNameIdMap.get(elem.pathLast); } else if (!parsedQuery.literalSearch) { let match = -1; let matchDist = maxEditDistance + 1; let matchName = ""; for (const [name, id] of typeNameIdMap) { - const dist = editDistance(name, elem.name, maxEditDistance); + const dist = editDistance(name, elem.pathLast, maxEditDistance); if (dist <= matchDist && dist <= maxEditDistance) { if (dist === matchDist && matchName > name) { continue; @@ -2385,10 +2411,19 @@ ${item.displayPath}${name}\ ); } // `0` is used as a sentinel because it's fewer bytes than `null` - const item = pathIndex === 0 ? null : lowercasePaths[pathIndex - 1]; + if (pathIndex === 0) { + return { + id: -1, + ty: null, + path: null, + generics: generics, + }; + } + const item = lowercasePaths[pathIndex - 1]; return { - id: item === null ? -1 : buildTypeMapIndex(item.name), - ty: item === null ? null : item.ty, + id: buildTypeMapIndex(item.name), + ty: item.ty, + path: item.path, generics: generics, }; }); @@ -2417,15 +2452,25 @@ ${item.displayPath}${name}\ if (functionSearchType === 0) { return null; } - let inputs, output, item; + let inputs, output; if (typeof functionSearchType[INPUTS_DATA] === "number") { const pathIndex = functionSearchType[INPUTS_DATA]; - item = pathIndex === 0 ? null : lowercasePaths[pathIndex - 1]; - inputs = [{ - id: item === null ? -1 : buildTypeMapIndex(item.name), - ty: item === null ? null : item.ty, - generics: [], - }]; + if (pathIndex === 0) { + inputs = [{ + id: -1, + ty: null, + path: null, + generics: [], + }]; + } else { + const item = lowercasePaths[pathIndex - 1]; + inputs = [{ + id: buildTypeMapIndex(item.name), + ty: item.ty, + path: item.path, + generics: [], + }]; + } } else { inputs = buildItemSearchTypeAll( functionSearchType[INPUTS_DATA], @@ -2435,12 +2480,22 @@ ${item.displayPath}${name}\ if (functionSearchType.length > 1) { if (typeof functionSearchType[OUTPUT_DATA] === "number") { const pathIndex = functionSearchType[OUTPUT_DATA]; - item = pathIndex === 0 ? null : lowercasePaths[pathIndex - 1]; - output = [{ - id: item === null ? -1 : buildTypeMapIndex(item.name), - ty: item === null ? null : item.ty, - generics: [], - }]; + if (pathIndex === 0) { + output = [{ + id: -1, + ty: null, + path: null, + generics: [], + }]; + } else { + const item = lowercasePaths[pathIndex - 1]; + output = [{ + id: buildTypeMapIndex(item.name), + ty: item.ty, + path: item.path, + generics: [], + }]; + } } else { output = buildItemSearchTypeAll( functionSearchType[OUTPUT_DATA], @@ -2573,9 +2628,19 @@ ${item.displayPath}${name}\ // convert `rawPaths` entries into object form // generate normalizedPaths for function search mode let len = paths.length; + let lastPath = itemPaths.get(0); for (let i = 0; i < len; ++i) { - lowercasePaths.push({ty: paths[i][0], name: paths[i][1].toLowerCase()}); - paths[i] = {ty: paths[i][0], name: paths[i][1]}; + const elem = paths[i]; + const ty = elem[0]; + const name = elem[1]; + let path = null; + if (elem.length > 2) { + path = itemPaths.has(elem[2]) ? itemPaths.get(elem[2]) : lastPath; + lastPath = path; + } + + lowercasePaths.push({ty: ty, name: name.toLowerCase(), path: path}); + paths[i] = {ty: ty, name: name, path: path}; } // convert `item*` into an object form, and construct word indices. @@ -2585,8 +2650,8 @@ ${item.displayPath}${name}\ // operation that is cached for the life of the page state so that // all other search operations have access to this cached data for // faster analysis operations + lastPath = ""; len = itemTypes.length; - let lastPath = ""; for (let i = 0; i < len; ++i) { let word = ""; // This object should have exactly the same set of fields as the "crateRow" @@ -2595,11 +2660,12 @@ ${item.displayPath}${name}\ word = itemNames[i].toLowerCase(); } searchWords.push(word); + const path = itemPaths.has(i) ? itemPaths.get(i) : lastPath; const row = { crate: crate, ty: itemTypes.charCodeAt(i) - charA, name: itemNames[i], - path: itemPaths.has(i) ? itemPaths.get(i) : lastPath, + path: path, desc: itemDescs[i], parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, type: buildFunctionSearchType( From 05eda416580ff9be12a6cf87238c8047a1699dd3 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 1 Sep 2023 14:31:01 +0200 Subject: [PATCH 3/5] Add tests for type-based search --- tests/rustdoc-js-std/full-path-function.js | 7 ++++++ tests/rustdoc-js/full-path-function.js | 25 ++++++++++++++++++++++ tests/rustdoc-js/full-path-function.rs | 16 ++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 tests/rustdoc-js-std/full-path-function.js create mode 100644 tests/rustdoc-js/full-path-function.js create mode 100644 tests/rustdoc-js/full-path-function.rs diff --git a/tests/rustdoc-js-std/full-path-function.js b/tests/rustdoc-js-std/full-path-function.js new file mode 100644 index 0000000000000..ac157b3aadf8e --- /dev/null +++ b/tests/rustdoc-js-std/full-path-function.js @@ -0,0 +1,7 @@ +const EXPECTED = { + 'query': 'vec::vec -> usize', + 'others': [ + { 'path': 'std::vec::Vec', 'name': 'len' }, + { 'path': 'std::vec::Vec', 'name': 'capacity' }, + ], +}; diff --git a/tests/rustdoc-js/full-path-function.js b/tests/rustdoc-js/full-path-function.js new file mode 100644 index 0000000000000..9d3c3a4506c40 --- /dev/null +++ b/tests/rustdoc-js/full-path-function.js @@ -0,0 +1,25 @@ +// exact-check + +const EXPECTED = [ + { + 'query': 'sac -> usize', + 'others': [ + { 'path': 'full_path_function::b::Sac', 'name': 'bar' }, + { 'path': 'full_path_function::b::Sac', 'name': 'len' }, + { 'path': 'full_path_function::sac::Sac', 'name': 'len' }, + ], + }, + { + 'query': 'b::sac -> usize', + 'others': [ + { 'path': 'full_path_function::b::Sac', 'name': 'bar' }, + { 'path': 'full_path_function::b::Sac', 'name': 'len' }, + ], + }, + { + 'query': 'b::sac -> u32', + 'others': [ + { 'path': 'full_path_function::b::Sac', 'name': 'bar2' }, + ], + }, +]; diff --git a/tests/rustdoc-js/full-path-function.rs b/tests/rustdoc-js/full-path-function.rs new file mode 100644 index 0000000000000..c1ec1e1731b13 --- /dev/null +++ b/tests/rustdoc-js/full-path-function.rs @@ -0,0 +1,16 @@ +pub mod sac { + pub struct Sac; + + impl Sac { + pub fn len(&self) -> usize { 0 } + } +} + +pub mod b { + pub struct Sac; + impl Sac { + pub fn len(&self) -> usize { 0 } + pub fn bar(&self, w: u32) -> usize { 0 } + pub fn bar2(&self, w: u32) -> u32 { 0 } + } +} From 36fa557a5e8ab96876157b44d199ad9b8e93d72c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 1 Sep 2023 14:46:47 +0200 Subject: [PATCH 4/5] Merge all loops into one when generating search index --- src/librustdoc/html/render/search_index.rs | 157 +++++++++------------ 1 file changed, 65 insertions(+), 92 deletions(-) diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index a7771c31f853b..2bbfb64ecb538 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -265,102 +265,75 @@ pub(crate) fn build_index<'tcx>( }) .collect::>(); + 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(&self, serializer: S) -> Result + 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); + } + } + 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::(), - )?; - crate_data.serialize_field( - "n", - &self.items.iter().map(|item| item.name.as_str()).collect::>(), - )?; - 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::>(), - )?; - crate_data.serialize_field( - "d", - &self.items.iter().map(|item| &item.desc).collect::>(), - )?; - 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::>(), - )?; - 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(&self, serializer: S) -> Result - 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::>(), - )?; - 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::>(), - )?; + 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)?; From e161fa1a6b3752da0a9279298eedf5b84a5fe49f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 2 Sep 2023 23:04:37 +0200 Subject: [PATCH 5/5] Correctly handle paths from foreign items --- src/librustdoc/html/render/search_index.rs | 57 +++++++++++++++++----- tests/rustdoc-js/full-path-function.js | 18 +++++++ tests/rustdoc-js/full-path-function.rs | 1 + 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 2bbfb64ecb538..145c7d18dd013 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -1,7 +1,7 @@ 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, SerializeSeq, SerializeStruct, Serializer}; @@ -246,6 +246,11 @@ pub(crate) fn build_index<'tcx>( 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() { @@ -253,17 +258,43 @@ pub(crate) fn build_index<'tcx>( } mod_paths.insert(&item.path, index); } - let paths = self - .paths - .iter() - .map(|(ty, path)| { - if path.len() < 2 { - return Paths { ty: *ty, name: path[0], path: None }; + 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()), + }); } - let index = mod_paths.get(&join_with_double_colon(&path[..path.len() - 1])); - Paths { ty: *ty, name: *path.last().unwrap(), path: index.copied() } - }) - .collect::>(); + 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()); @@ -322,6 +353,10 @@ pub(crate) fn build_index<'tcx>( } } + 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 })?; diff --git a/tests/rustdoc-js/full-path-function.js b/tests/rustdoc-js/full-path-function.js index 9d3c3a4506c40..48be51b156fde 100644 --- a/tests/rustdoc-js/full-path-function.js +++ b/tests/rustdoc-js/full-path-function.js @@ -22,4 +22,22 @@ const EXPECTED = [ { 'path': 'full_path_function::b::Sac', 'name': 'bar2' }, ], }, + { + 'query': 'string::string -> u32', + 'others': [ + { 'path': 'full_path_function::b::Sac', 'name': 'string' }, + ], + }, + { + 'query': 'alloc::string::string -> u32', + 'others': [ + { 'path': 'full_path_function::b::Sac', 'name': 'string' }, + ], + }, + { + 'query': 'alloc::string -> u32', + 'others': [ + { 'path': 'full_path_function::b::Sac', 'name': 'string' }, + ], + }, ]; diff --git a/tests/rustdoc-js/full-path-function.rs b/tests/rustdoc-js/full-path-function.rs index c1ec1e1731b13..8dcc3f2b69d84 100644 --- a/tests/rustdoc-js/full-path-function.rs +++ b/tests/rustdoc-js/full-path-function.rs @@ -12,5 +12,6 @@ pub mod b { pub fn len(&self) -> usize { 0 } pub fn bar(&self, w: u32) -> usize { 0 } pub fn bar2(&self, w: u32) -> u32 { 0 } + pub fn string(w: String) -> u32 { 0 } } }