Auto merge of #109802 - notriddle:notriddle/rustdoc-search-generics-nested, r=GuillaumeGomez

rustdoc-search: add support for nested generics

This change allows `search.js` to parse nested generics (which look `Like<This<Example>>`) and match them. It maintains the existing "bag semantics", so that the order of type parameters is ignored but the number is required to be greater than or equal to what's in the query.

For example, a function with the signature `fn read_all(&mut self: impl Read) -> Result<Vec<u8>, Error>` will match these queries:

* `Read -> Result<Vec<u8>, Error>`
* `Read -> Result<Error, Vec>`
* `Read -> Result<Vec<u8>>`

But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
This commit is contained in:
bors 2023-04-15 02:23:32 +00:00
commit 3312a3053b
5 changed files with 195 additions and 13 deletions

View File

@ -94,6 +94,17 @@ can be matched with the following queries:
* `trait:Iterator<primitive:u32> -> primitive:usize`
* `Iterator -> usize`
Generics and function parameters are order-agnostic, but sensitive to nesting
and number of matches. For example, a function with the signature
`fn read_all(&mut self: impl Read) -> Result<Vec<u8>, Error>`
will match these queries:
* `Read -> Result<Vec<u8>, Error>`
* `Read -> Result<Error, Vec>`
* `Read -> Result<Vec<u8>>`
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
### Changing displayed theme
You can change the displayed theme by opening the settings menu (the gear

View File

@ -461,9 +461,7 @@ function initSearch(rawSearchIndex) {
if (parserState.pos < parserState.length &&
parserState.userQuery[parserState.pos] === "<"
) {
if (isInGenerics) {
throw ["Unexpected ", "<", " after ", "<"];
} else if (start >= end) {
if (start >= end) {
throw ["Found generics without a path"];
}
parserState.pos += 1;
@ -765,13 +763,10 @@ function initSearch(rawSearchIndex) {
* ident = *(ALPHA / DIGIT / "_")
* path = ident *(DOUBLE-COLON ident) [!]
* arg = [type-filter *WS COLON *WS] path [generics]
* arg-without-generic = [type-filter *WS COLON *WS] path
* type-sep = COMMA/WS *(COMMA/WS)
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
* nonempty-arg-list-without-generics = *(type-sep) arg-without-generic
* *(type-sep arg-without-generic) *(type-sep)
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list-without-generics ] *(type-sep)
* CLOSE-ANGLE-BRACKET/EOF
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
* CLOSE-ANGLE-BRACKET
* return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
*
* exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
@ -1127,7 +1122,7 @@ function initSearch(rawSearchIndex) {
currentEntryElems = [];
elems.set(entry.name, currentEntryElems);
}
currentEntryElems.push(entry.ty);
currentEntryElems.push(entry);
}
// We need to find the type that matches the most to remove it in order
// to move forward.
@ -1136,8 +1131,12 @@ function initSearch(rawSearchIndex) {
return false;
}
const matchElems = elems.get(generic.name);
const matchIdx = matchElems.findIndex(tmp_elem =>
typePassesFilter(generic.typeFilter, tmp_elem));
const matchIdx = matchElems.findIndex(tmp_elem => {
if (checkGenerics(tmp_elem, generic, 0, maxEditDistance) !== 0) {
return false;
}
return typePassesFilter(generic.typeFilter, tmp_elem.ty);
});
if (matchIdx === -1) {
return false;
}

View File

@ -1,4 +1,11 @@
const QUERY = ['A<B<C<D>, E>', 'p<> u8', '"p"<a>'];
const QUERY = [
'A<B<C<D>, E>',
'p<> u8',
'"p"<a>',
'p<u<x>>',
'p<u<x>, r>',
'p<u<x, r>>',
];
const PARSED = [
{
@ -7,7 +14,7 @@ const PARSED = [
original: 'A<B<C<D>, E>',
returned: [],
userQuery: 'a<b<c<d>, e>',
error: 'Unexpected `<` after `<`',
error: 'Unclosed `<`',
},
{
elems: [
@ -59,4 +66,117 @@ const PARSED = [
userQuery: '"p"<a>',
error: null,
},
{
elems: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [
{
name: "u",
fullPath: ["u"],
pathWithoutLast: [],
pathLast: "u",
generics: [
{
name: "x",
fullPath: ["x"],
pathWithoutLast: [],
pathLast: "x",
generics: [],
},
],
},
],
typeFilter: -1,
},
],
foundElems: 1,
original: 'p<u<x>>',
returned: [],
userQuery: 'p<u<x>>',
error: null,
},
{
elems: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [
{
name: "u",
fullPath: ["u"],
pathWithoutLast: [],
pathLast: "u",
generics: [
{
name: "x",
fullPath: ["x"],
pathWithoutLast: [],
pathLast: "x",
generics: [],
},
],
},
{
name: "r",
fullPath: ["r"],
pathWithoutLast: [],
pathLast: "r",
generics: [],
},
],
typeFilter: -1,
},
],
foundElems: 1,
original: 'p<u<x>, r>',
returned: [],
userQuery: 'p<u<x>, r>',
error: null,
},
{
elems: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [
{
name: "u",
fullPath: ["u"],
pathWithoutLast: [],
pathLast: "u",
generics: [
{
name: "x",
fullPath: ["x"],
pathWithoutLast: [],
pathLast: "x",
generics: [],
},
{
name: "r",
fullPath: ["r"],
pathWithoutLast: [],
pathLast: "r",
generics: [],
},
],
},
],
typeFilter: -1,
},
],
foundElems: 1,
original: 'p<u<x, r>>',
returned: [],
userQuery: 'p<u<x, r>>',
error: null,
},
];

View File

@ -0,0 +1,33 @@
// exact-check
const QUERY = [
'-> Out<First<Second>>',
'-> Out<Second<First>>',
'-> Out<First, Second>',
'-> Out<Second, First>',
];
const EXPECTED = [
{
// -> Out<First<Second>>
'others': [
{ 'path': 'generics_nested', 'name': 'alef' },
],
},
{
// -> Out<Second<First>>
'others': [],
},
{
// -> Out<First, Second>
'others': [
{ 'path': 'generics_nested', 'name': 'bet' },
],
},
{
// -> Out<Second, First>
'others': [
{ 'path': 'generics_nested', 'name': 'bet' },
],
},
];

View File

@ -0,0 +1,19 @@
pub struct Out<A, B = ()> {
a: A,
b: B,
}
pub struct First<In = ()> {
in_: In,
}
pub struct Second;
// Out<First<Second>>
pub fn alef() -> Out<First<Second>> {
loop {}
}
pub fn bet() -> Out<First, Second> {
loop {}
}