Skip to content

Commit 94badbe

Browse files
committed
rustdoc-search: fix order-independence bug
1 parent c897ded commit 94badbe

File tree

4 files changed

+207
-72
lines changed

4 files changed

+207
-72
lines changed

src/librustdoc/html/static/js/search.js

+98-68
Original file line numberDiff line numberDiff line change
@@ -1208,13 +1208,62 @@ function initSearch(rawSearchIndex) {
12081208
if (!fnTypes || fnTypes.length === 0) {
12091209
return false;
12101210
}
1211+
/**
1212+
* @type Map<integer, QueryElement[]>
1213+
*/
1214+
const queryElemSet = new Map();
1215+
const addQueryElemToQueryElemSet = function addQueryElemToQueryElemSet(queryElem) {
1216+
let currentQueryElemList;
1217+
if (queryElemSet.has(queryElem.id)) {
1218+
currentQueryElemList = queryElemSet.get(queryElem.id);
1219+
} else {
1220+
currentQueryElemList = [];
1221+
queryElemSet.set(queryElem.id, currentQueryElemList);
1222+
}
1223+
currentQueryElemList.push(queryElem);
1224+
};
1225+
for (const queryElem of queryElems) {
1226+
addQueryElemToQueryElemSet(queryElem);
1227+
}
12111228
/**
12121229
* @type Map<integer, FunctionType[]>
12131230
*/
12141231
const fnTypeSet = new Map();
12151232
const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
1216-
if (fnType.id === -1) {
1217-
// Pure generic, needs to check into it.
1233+
// Pure generic, or an item that's not matched by any query elems.
1234+
// Try [unboxing] it.
1235+
//
1236+
// [unboxing]:
1237+
// http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
1238+
const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
1239+
if (fnType.id === -1 || !(
1240+
queryElemSet.has(fnType.id) ||
1241+
(fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
1242+
(fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem)
1243+
)) {
1244+
for (const innerFnType of fnType.generics) {
1245+
addFnTypeToFnTypeSet(innerFnType);
1246+
}
1247+
return;
1248+
}
1249+
let currentQueryElemList = queryElemSet.get(fnType.id) || [];
1250+
let matchIdx = currentQueryElemList.findIndex(queryElem => {
1251+
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
1252+
checkGenerics(fnType, queryElem);
1253+
});
1254+
if (matchIdx === -1 &&
1255+
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) &&
1256+
queryContainsArrayOrSliceElem
1257+
) {
1258+
currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || [];
1259+
matchIdx = currentQueryElemList.findIndex(queryElem => {
1260+
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
1261+
checkGenerics(fnType, queryElem);
1262+
});
1263+
}
1264+
// None of the query elems match the function type.
1265+
// Try [unboxing] it.
1266+
if (matchIdx === -1) {
12181267
for (const innerFnType of fnType.generics) {
12191268
addFnTypeToFnTypeSet(innerFnType);
12201269
}
@@ -1232,85 +1281,66 @@ function initSearch(rawSearchIndex) {
12321281
for (const fnType of fnTypes) {
12331282
addFnTypeToFnTypeSet(fnType);
12341283
}
1235-
// We need to find the type that matches the most to remove it in order
1236-
// to move forward.
1237-
const handleQueryElem = queryElem => {
1238-
if (!fnTypeSet.has(queryElem.id)) {
1239-
return false;
1284+
const doHandleQueryElemList = (currentFnTypeList, queryElemList) => {
1285+
if (queryElemList.length === 0) {
1286+
return true;
12401287
}
1241-
const currentFnTypeList = fnTypeSet.get(queryElem.id);
1242-
const matchIdx = currentFnTypeList.findIndex(fnType => {
1288+
// Multiple items in one list might match multiple items in another.
1289+
// Since an item with fewer generics can match an item with more, we
1290+
// need to check all combinations for a potential match.
1291+
const queryElem = queryElemList.pop();
1292+
const l = currentFnTypeList.length;
1293+
for (let i = 0; i < l; i += 1) {
1294+
const fnType = currentFnTypeList[i];
12431295
if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
1244-
return false;
1296+
continue;
1297+
}
1298+
if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) {
1299+
currentFnTypeList.splice(i, 1);
1300+
const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
1301+
if (result) {
1302+
return true;
1303+
}
1304+
currentFnTypeList.splice(i, 0, fnType);
12451305
}
1246-
return queryElem.generics.length === 0 || checkGenerics(fnType, queryElem);
1247-
});
1248-
if (matchIdx === -1) {
1249-
return false;
1250-
}
1251-
currentFnTypeList.splice(matchIdx, 1);
1252-
if (currentFnTypeList.length === 0) {
1253-
fnTypeSet.delete(queryElem.id);
12541306
}
1255-
return true;
1307+
return false;
12561308
};
1257-
// To do the right thing with type filters, we first process generics
1258-
// that have them, removing matching ones from the "bag," then do the
1259-
// ones with no type filter, which can match any entry regardless of its
1260-
// own type.
1261-
const needsUnboxed = [];
1262-
for (const queryElem of queryElems) {
1263-
if (queryElem.typeFilter === TY_PRIMITIVE &&
1264-
queryElem.id === typeNameIdOfArrayOrSlice) {
1265-
const queryElemArray = {
1266-
id: typeNameIdOfArray,
1267-
typeFilter: TY_PRIMITIVE,
1268-
generics: queryElem.generics,
1269-
};
1270-
const queryElemSlice = {
1271-
id: typeNameIdOfSlice,
1272-
typeFilter: TY_PRIMITIVE,
1273-
generics: queryElem.generics,
1274-
};
1275-
if (!handleQueryElem(queryElemArray) && !handleQueryElem(queryElemSlice)) {
1276-
needsUnboxed.push(queryElem);
1309+
const handleQueryElemList = (id, queryElemList) => {
1310+
if (!fnTypeSet.has(id)) {
1311+
if (id === typeNameIdOfArrayOrSlice) {
1312+
return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
1313+
handleQueryElemList(typeNameIdOfArray, queryElemList);
12771314
}
1278-
} else if (queryElem.typeFilter !== -1 && !handleQueryElem(queryElem)) {
1279-
needsUnboxed.push(queryElem);
1315+
return false;
12801316
}
1281-
}
1282-
for (const queryElem of queryElems) {
1283-
if (queryElem.typeFilter === -1 && !handleQueryElem(queryElem)) {
1284-
needsUnboxed.push(queryElem);
1317+
const currentFnTypeList = fnTypeSet.get(id);
1318+
if (currentFnTypeList.length < queryElemList.length) {
1319+
// It's not possible for all the query elems to find a match.
1320+
return false;
12851321
}
1286-
}
1287-
// If the current item does not match, try [unboxing] the generic.
1288-
// [unboxing]:
1289-
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
1290-
unboxing: while (needsUnboxed.length !== 0) {
1291-
for (const [i, queryElem] of needsUnboxed.entries()) {
1292-
if (handleQueryElem(queryElem)) {
1293-
needsUnboxed.splice(i, 1);
1294-
continue unboxing;
1322+
const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
1323+
if (result) {
1324+
// Found a solution.
1325+
// Any items that weren't used for it can be unboxed, and might form
1326+
// part of the solution for another item.
1327+
for (const innerFnType of currentFnTypeList) {
1328+
addFnTypeToFnTypeSet(innerFnType);
12951329
}
1330+
fnTypeSet.delete(id);
12961331
}
1297-
for (const [id, fnTypeList] of fnTypeSet) {
1298-
for (const [i, fnType] of fnTypeList.entries()) {
1299-
if (fnType.generics.length !== 0) {
1300-
fnTypeList.splice(i, 1);
1301-
for (const innerFnType of fnType.generics) {
1302-
addFnTypeToFnTypeSet(innerFnType);
1303-
}
1304-
if (fnTypeList.length === 0) {
1305-
fnTypeSet.delete(id);
1306-
}
1307-
continue unboxing;
1308-
}
1332+
return result;
1333+
};
1334+
let queryElemSetSize = -1;
1335+
while (queryElemSetSize !== queryElemSet.size) {
1336+
queryElemSetSize = queryElemSet.size;
1337+
for (const [id, queryElemList] of queryElemSet) {
1338+
if (handleQueryElemList(id, queryElemList)) {
1339+
queryElemSet.delete(id);
13091340
}
13101341
}
1311-
return false;
13121342
}
1313-
return true;
1343+
return queryElemSetSize === 0;
13141344
}
13151345

13161346
/**

tests/rustdoc-js-std/bufread-fill-buf.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
// ignore-order
22

3-
const QUERY = [
4-
'bufread -> result<u8>',
5-
];
6-
73
const EXPECTED = [
84
{
5+
'query': 'bufread -> result<u8>',
96
'others': [
107
{ 'path': 'std::io::Split', 'name': 'next' },
118
{ 'path': 'std::boxed::Box', 'name': 'fill_buf' },
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// ignore-order
2+
// exact-check
3+
4+
// Make sure that results are order-agnostic, even when there's search items that only differ
5+
// by generics.
6+
7+
const EXPECTED = [
8+
{
9+
'query': 'Wrap',
10+
'in_args': [
11+
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
12+
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
13+
],
14+
},
15+
{
16+
'query': 'Wrap<i32>',
17+
'in_args': [
18+
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
19+
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
20+
],
21+
},
22+
{
23+
'query': 'Wrap<i32>, Wrap<i32, u32>',
24+
'others': [
25+
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
26+
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
27+
],
28+
},
29+
{
30+
'query': 'Wrap<i32, u32>, Wrap<i32>',
31+
'others': [
32+
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
33+
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
34+
],
35+
},
36+
{
37+
'query': 'W3<i32>, W3<i32, u32>',
38+
'others': [
39+
{ 'path': 'generics_match_ambiguity', 'name': 'baaa' },
40+
{ 'path': 'generics_match_ambiguity', 'name': 'baab' },
41+
{ 'path': 'generics_match_ambiguity', 'name': 'baac' },
42+
{ 'path': 'generics_match_ambiguity', 'name': 'baad' },
43+
{ 'path': 'generics_match_ambiguity', 'name': 'baae' },
44+
{ 'path': 'generics_match_ambiguity', 'name': 'baaf' },
45+
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
46+
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
47+
],
48+
},
49+
{
50+
'query': 'W3<i32, u32>, W3<i32>',
51+
'others': [
52+
{ 'path': 'generics_match_ambiguity', 'name': 'baaa' },
53+
{ 'path': 'generics_match_ambiguity', 'name': 'baab' },
54+
{ 'path': 'generics_match_ambiguity', 'name': 'baac' },
55+
{ 'path': 'generics_match_ambiguity', 'name': 'baad' },
56+
{ 'path': 'generics_match_ambiguity', 'name': 'baae' },
57+
{ 'path': 'generics_match_ambiguity', 'name': 'baaf' },
58+
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
59+
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
60+
],
61+
},
62+
{
63+
'query': 'W2<i32>, W2<i32, u32>',
64+
'others': [
65+
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
66+
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
67+
],
68+
},
69+
{
70+
'query': 'W2<i32, u32>, W2<i32>',
71+
'others': [
72+
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
73+
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
74+
],
75+
},
76+
{
77+
'query': 'W2<i32>, W3<i32, u32>',
78+
'others': [
79+
{ 'path': 'generics_match_ambiguity', 'name': 'baac' },
80+
{ 'path': 'generics_match_ambiguity', 'name': 'baaf' },
81+
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
82+
],
83+
},
84+
{
85+
'query': 'W2<i32>, W2<i32>',
86+
'others': [
87+
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
88+
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
89+
],
90+
},
91+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
pub struct Wrap<T, U = ()>(pub T, pub U);
2+
3+
pub fn foo(a: Wrap<i32>, b: Wrap<i32, u32>) {}
4+
pub fn bar(a: Wrap<i32, u32>, b: Wrap<i32>) {}
5+
6+
pub struct W2<T>(pub T);
7+
pub struct W3<T, U = ()>(pub T, pub U);
8+
9+
pub fn baaa(a: W3<i32>, b: W3<i32, u32>) {}
10+
pub fn baab(a: W3<i32, u32>, b: W3<i32>) {}
11+
pub fn baac(a: W2<W3<i32>>, b: W3<i32, u32>) {}
12+
pub fn baad(a: W2<W3<i32, u32>>, b: W3<i32>) {}
13+
pub fn baae(a: W3<i32>, b: W2<W3<i32, u32>>) {}
14+
pub fn baaf(a: W3<i32, u32>, b: W2<W3<i32>>) {}
15+
pub fn baag(a: W2<W3<i32>>, b: W2<W3<i32, u32>>) {}
16+
pub fn baah(a: W2<W3<i32, u32>>, b: W2<W3<i32>>) {}
17+
//

0 commit comments

Comments
 (0)