diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index f34be120d292b..145c7d18dd013 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};
@@ -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,118 +213,163 @@ 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 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(&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);
+ }
+ }
+
+ 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::(),
- )?;
- 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(
- "p",
- &self.paths.iter().map(|(it, s)| (it, s.as_str())).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)?;
}
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 42088e7355440..449168c460bed 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.
//
@@ -1463,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);
@@ -1863,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,12 +2410,20 @@ ${item.displayPath}${name}\
lowercasePaths
);
}
+ // `0` is used as a sentinel because it's fewer bytes than `null`
+ if (pathIndex === 0) {
+ return {
+ id: -1,
+ ty: null,
+ path: null,
+ generics: generics,
+ };
+ }
+ const item = 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: buildTypeMapIndex(item.name),
+ ty: item.ty,
+ path: item.path,
generics: generics,
};
});
@@ -2422,13 +2455,22 @@ ${item.displayPath}${name}\
let inputs, output;
if (typeof functionSearchType[INPUTS_DATA] === "number") {
const pathIndex = functionSearchType[INPUTS_DATA];
- inputs = [{
- id: pathIndex === 0
- ? -1
- : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
- ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].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],
@@ -2438,13 +2480,22 @@ ${item.displayPath}${name}\
if (functionSearchType.length > 1) {
if (typeof functionSearchType[OUTPUT_DATA] === "number") {
const pathIndex = functionSearchType[OUTPUT_DATA];
- output = [{
- id: pathIndex === 0
- ? -1
- : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
- ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].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],
@@ -2577,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.
@@ -2589,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"
@@ -2599,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(
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..48be51b156fde
--- /dev/null
+++ b/tests/rustdoc-js/full-path-function.js
@@ -0,0 +1,43 @@
+// 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' },
+ ],
+ },
+ {
+ '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
new file mode 100644
index 0000000000000..8dcc3f2b69d84
--- /dev/null
+++ b/tests/rustdoc-js/full-path-function.rs
@@ -0,0 +1,17 @@
+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 }
+ pub fn string(w: String) -> u32 { 0 }
+ }
+}