Skip to content

Commit 3ec4b3b

Browse files
committed
Auto merge of #115436 - GuillaumeGomez:fix-type-based-search, r=notriddle
[rustdoc] Fix type based search Fixes #114522. The problem was a bit more tricky than I originally thought it would be: we only kept type ID and generics in short, but as soon as there was a full path in the user query, the element didn't get an ID anymore because the ID map didn't know about `x::y` (although it knew about `y`). So for this first problem, I instead always pass the element name to get the ID. Then a new problem occurred: we actually needed to check if paths matched, otherwise whatever the path, as long as the "end types" match, it's all good. meaning, we needed to add path information, but to do so, we needed it to be added into the search index directly as there was no mapping between `"p"` and `"q"`. I hope this explanation makes sense to someone else than me. ^^' r? `@notriddle`
2 parents b588641 + e161fa1 commit 3ec4b3b

File tree

5 files changed

+309
-135
lines changed

5 files changed

+309
-135
lines changed

src/librustdoc/html/render/search_index.rs

+151-106
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::collections::hash_map::Entry;
22
use std::collections::BTreeMap;
33

4-
use rustc_data_structures::fx::FxHashMap;
4+
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
55
use rustc_middle::ty::TyCtxt;
66
use rustc_span::symbol::Symbol;
7-
use serde::ser::{Serialize, SerializeStruct, Serializer};
7+
use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
88

99
use crate::clean;
1010
use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
@@ -78,17 +78,17 @@ pub(crate) fn build_index<'tcx>(
7878
map: &mut FxHashMap<F, usize>,
7979
itemid: F,
8080
lastpathid: &mut usize,
81-
crate_paths: &mut Vec<(ItemType, Symbol)>,
81+
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
8282
item_type: ItemType,
83-
path: Symbol,
83+
path: &[Symbol],
8484
) {
8585
match map.entry(itemid) {
8686
Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())),
8787
Entry::Vacant(entry) => {
8888
let pathid = *lastpathid;
8989
entry.insert(pathid);
9090
*lastpathid += 1;
91-
crate_paths.push((item_type, path));
91+
crate_paths.push((item_type, path.to_vec()));
9292
ty.id = Some(RenderTypeId::Index(pathid));
9393
}
9494
}
@@ -100,7 +100,7 @@ pub(crate) fn build_index<'tcx>(
100100
itemid_to_pathid: &mut FxHashMap<ItemId, usize>,
101101
primitives: &mut FxHashMap<Symbol, usize>,
102102
lastpathid: &mut usize,
103-
crate_paths: &mut Vec<(ItemType, Symbol)>,
103+
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
104104
) {
105105
if let Some(generics) = &mut ty.generics {
106106
for item in generics {
@@ -131,7 +131,7 @@ pub(crate) fn build_index<'tcx>(
131131
lastpathid,
132132
crate_paths,
133133
item_type,
134-
*fqp.last().unwrap(),
134+
fqp,
135135
);
136136
} else {
137137
ty.id = None;
@@ -146,7 +146,7 @@ pub(crate) fn build_index<'tcx>(
146146
lastpathid,
147147
crate_paths,
148148
ItemType::Primitive,
149-
sym,
149+
&[sym],
150150
);
151151
}
152152
RenderTypeId::Index(_) => {}
@@ -191,7 +191,7 @@ pub(crate) fn build_index<'tcx>(
191191
lastpathid += 1;
192192

193193
if let Some(&(ref fqp, short)) = paths.get(&defid) {
194-
crate_paths.push((short, *fqp.last().unwrap()));
194+
crate_paths.push((short, fqp.clone()));
195195
Some(pathid)
196196
} else {
197197
None
@@ -213,118 +213,163 @@ pub(crate) fn build_index<'tcx>(
213213
struct CrateData<'a> {
214214
doc: String,
215215
items: Vec<&'a IndexItem>,
216-
paths: Vec<(ItemType, Symbol)>,
216+
paths: Vec<(ItemType, Vec<Symbol>)>,
217217
// The String is alias name and the vec is the list of the elements with this alias.
218218
//
219219
// To be noted: the `usize` elements are indexes to `items`.
220220
aliases: &'a BTreeMap<String, Vec<usize>>,
221221
}
222222

223+
struct Paths {
224+
ty: ItemType,
225+
name: Symbol,
226+
path: Option<usize>,
227+
}
228+
229+
impl Serialize for Paths {
230+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
231+
where
232+
S: Serializer,
233+
{
234+
let mut seq = serializer.serialize_seq(None)?;
235+
seq.serialize_element(&self.ty)?;
236+
seq.serialize_element(self.name.as_str())?;
237+
if let Some(ref path) = self.path {
238+
seq.serialize_element(path)?;
239+
}
240+
seq.end()
241+
}
242+
}
243+
223244
impl<'a> Serialize for CrateData<'a> {
224245
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
225246
where
226247
S: Serializer,
227248
{
249+
let mut extra_paths = FxHashMap::default();
250+
// We need to keep the order of insertion, hence why we use an `IndexMap`. Then we will
251+
// insert these "extra paths" (which are paths of items from external crates) into the
252+
// `full_paths` list at the end.
253+
let mut revert_extra_paths = FxIndexMap::default();
254+
let mut mod_paths = FxHashMap::default();
255+
for (index, item) in self.items.iter().enumerate() {
256+
if item.path.is_empty() {
257+
continue;
258+
}
259+
mod_paths.insert(&item.path, index);
260+
}
261+
let mut paths = Vec::with_capacity(self.paths.len());
262+
for (ty, path) in &self.paths {
263+
if path.len() < 2 {
264+
paths.push(Paths { ty: *ty, name: path[0], path: None });
265+
continue;
266+
}
267+
let full_path = join_with_double_colon(&path[..path.len() - 1]);
268+
if let Some(index) = mod_paths.get(&full_path) {
269+
paths.push(Paths { ty: *ty, name: *path.last().unwrap(), path: Some(*index) });
270+
continue;
271+
}
272+
// It means it comes from an external crate so the item and its path will be
273+
// stored into another array.
274+
//
275+
// `index` is put after the last `mod_paths`
276+
let index = extra_paths.len() + self.items.len();
277+
if !revert_extra_paths.contains_key(&index) {
278+
revert_extra_paths.insert(index, full_path.clone());
279+
}
280+
match extra_paths.entry(full_path) {
281+
Entry::Occupied(entry) => {
282+
paths.push(Paths {
283+
ty: *ty,
284+
name: *path.last().unwrap(),
285+
path: Some(*entry.get()),
286+
});
287+
}
288+
Entry::Vacant(entry) => {
289+
entry.insert(index);
290+
paths.push(Paths {
291+
ty: *ty,
292+
name: *path.last().unwrap(),
293+
path: Some(index),
294+
});
295+
}
296+
}
297+
}
298+
299+
let mut names = Vec::with_capacity(self.items.len());
300+
let mut types = String::with_capacity(self.items.len());
301+
let mut full_paths = Vec::with_capacity(self.items.len());
302+
let mut descriptions = Vec::with_capacity(self.items.len());
303+
let mut parents = Vec::with_capacity(self.items.len());
304+
let mut functions = Vec::with_capacity(self.items.len());
305+
let mut deprecated = Vec::with_capacity(self.items.len());
306+
307+
for (index, item) in self.items.iter().enumerate() {
308+
let n = item.ty as u8;
309+
let c = char::try_from(n + b'A').expect("item types must fit in ASCII");
310+
assert!(c <= 'z', "item types must fit within ASCII printables");
311+
types.push(c);
312+
313+
assert_eq!(
314+
item.parent.is_some(),
315+
item.parent_idx.is_some(),
316+
"`{}` is missing idx",
317+
item.name
318+
);
319+
// 0 is a sentinel, everything else is one-indexed
320+
parents.push(item.parent_idx.map(|x| x + 1).unwrap_or(0));
321+
322+
names.push(item.name.as_str());
323+
descriptions.push(&item.desc);
324+
325+
if !item.path.is_empty() {
326+
full_paths.push((index, &item.path));
327+
}
328+
329+
// Fake option to get `0` out as a sentinel instead of `null`.
330+
// We want to use `0` because it's three less bytes.
331+
enum FunctionOption<'a> {
332+
Function(&'a IndexItemFunctionType),
333+
None,
334+
}
335+
impl<'a> Serialize for FunctionOption<'a> {
336+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
337+
where
338+
S: Serializer,
339+
{
340+
match self {
341+
FunctionOption::None => 0.serialize(serializer),
342+
FunctionOption::Function(ty) => ty.serialize(serializer),
343+
}
344+
}
345+
}
346+
functions.push(match &item.search_type {
347+
Some(ty) => FunctionOption::Function(ty),
348+
None => FunctionOption::None,
349+
});
350+
351+
if item.deprecation.is_some() {
352+
deprecated.push(index);
353+
}
354+
}
355+
356+
for (index, path) in &revert_extra_paths {
357+
full_paths.push((*index, path));
358+
}
359+
228360
let has_aliases = !self.aliases.is_empty();
229361
let mut crate_data =
230362
serializer.serialize_struct("CrateData", if has_aliases { 9 } else { 8 })?;
231363
crate_data.serialize_field("doc", &self.doc)?;
232-
crate_data.serialize_field(
233-
"t",
234-
&self
235-
.items
236-
.iter()
237-
.map(|item| {
238-
let n = item.ty as u8;
239-
let c = char::try_from(n + b'A').expect("item types must fit in ASCII");
240-
assert!(c <= 'z', "item types must fit within ASCII printables");
241-
c
242-
})
243-
.collect::<String>(),
244-
)?;
245-
crate_data.serialize_field(
246-
"n",
247-
&self.items.iter().map(|item| item.name.as_str()).collect::<Vec<_>>(),
248-
)?;
249-
crate_data.serialize_field(
250-
"q",
251-
&self
252-
.items
253-
.iter()
254-
.enumerate()
255-
// Serialize as an array of item indices and full paths
256-
.filter_map(
257-
|(index, item)| {
258-
if item.path.is_empty() { None } else { Some((index, &item.path)) }
259-
},
260-
)
261-
.collect::<Vec<_>>(),
262-
)?;
263-
crate_data.serialize_field(
264-
"d",
265-
&self.items.iter().map(|item| &item.desc).collect::<Vec<_>>(),
266-
)?;
267-
crate_data.serialize_field(
268-
"i",
269-
&self
270-
.items
271-
.iter()
272-
.map(|item| {
273-
assert_eq!(
274-
item.parent.is_some(),
275-
item.parent_idx.is_some(),
276-
"`{}` is missing idx",
277-
item.name
278-
);
279-
// 0 is a sentinel, everything else is one-indexed
280-
item.parent_idx.map(|x| x + 1).unwrap_or(0)
281-
})
282-
.collect::<Vec<_>>(),
283-
)?;
284-
crate_data.serialize_field(
285-
"f",
286-
&self
287-
.items
288-
.iter()
289-
.map(|item| {
290-
// Fake option to get `0` out as a sentinel instead of `null`.
291-
// We want to use `0` because it's three less bytes.
292-
enum FunctionOption<'a> {
293-
Function(&'a IndexItemFunctionType),
294-
None,
295-
}
296-
impl<'a> Serialize for FunctionOption<'a> {
297-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
298-
where
299-
S: Serializer,
300-
{
301-
match self {
302-
FunctionOption::None => 0.serialize(serializer),
303-
FunctionOption::Function(ty) => ty.serialize(serializer),
304-
}
305-
}
306-
}
307-
match &item.search_type {
308-
Some(ty) => FunctionOption::Function(ty),
309-
None => FunctionOption::None,
310-
}
311-
})
312-
.collect::<Vec<_>>(),
313-
)?;
314-
crate_data.serialize_field(
315-
"c",
316-
&self
317-
.items
318-
.iter()
319-
.enumerate()
320-
// Serialize as an array of deprecated item indices
321-
.filter_map(|(index, item)| item.deprecation.map(|_| index))
322-
.collect::<Vec<_>>(),
323-
)?;
324-
crate_data.serialize_field(
325-
"p",
326-
&self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::<Vec<_>>(),
327-
)?;
364+
crate_data.serialize_field("t", &types)?;
365+
crate_data.serialize_field("n", &names)?;
366+
// Serialize as an array of item indices and full paths
367+
crate_data.serialize_field("q", &full_paths)?;
368+
crate_data.serialize_field("d", &descriptions)?;
369+
crate_data.serialize_field("i", &parents)?;
370+
crate_data.serialize_field("f", &functions)?;
371+
crate_data.serialize_field("c", &deprecated)?;
372+
crate_data.serialize_field("p", &paths)?;
328373
if has_aliases {
329374
crate_data.serialize_field("a", &self.aliases)?;
330375
}

0 commit comments

Comments
 (0)