Skip to content

Commit e9f5299

Browse files
committed
rustdoc-search: add search query syntax Fn(T) -> U
This is implemented, in addition to the ML-style one, because Rust does it. If we don't, we'll never hear the end of it. This commit also refactors some duplicate parts of the parser into a dedicated function.
1 parent df043c4 commit e9f5299

File tree

6 files changed

+529
-78
lines changed

6 files changed

+529
-78
lines changed

src/doc/rustdoc/src/read-documentation/search.md

+30-13
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,26 @@ will match these queries:
153153

154154
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
155155

156+
To search for a function that accepts a function as a parameter,
157+
like `Iterator::all`, wrap the nested signature in parenthesis,
158+
as in [`Iterator<T>, (T -> bool) -> bool`][iterator-all].
159+
You can also search for a specific closure trait,
160+
such as `Iterator<T>, (FnMut(T) -> bool) -> bool`,
161+
but you need to know which one you want.
162+
163+
[iterator-all]: ../../std/vec/struct.Vec.html?search=Iterator<T>%2C+(T+->+bool)+->+bool&filter-crate=std
164+
156165
### Primitives with Special Syntax
157166

158-
| Shorthand | Explicit names |
159-
| --------- | ------------------------------------------------ |
160-
| `[]` | `primitive:slice` and/or `primitive:array` |
161-
| `[T]` | `primitive:slice<T>` and/or `primitive:array<T>` |
162-
| `()` | `primitive:unit` and/or `primitive:tuple` |
163-
| `(T)` | `T` |
164-
| `(T,)` | `primitive:tuple<T>` |
165-
| `!` | `primitive:never` |
167+
| Shorthand | Explicit names |
168+
| ---------------- | ------------------------------------------------- |
169+
| `[]` | `primitive:slice` and/or `primitive:array` |
170+
| `[T]` | `primitive:slice<T>` and/or `primitive:array<T>` |
171+
| `()` | `primitive:unit` and/or `primitive:tuple` |
172+
| `(T)` | `T` |
173+
| `(T,)` | `primitive:tuple<T>` |
174+
| `!` | `primitive:never` |
175+
| `(T, U -> V, W)` | `fn(T, U) -> (V, W)`, `Fn`, `FnMut`, and `FnOnce` |
166176

167177
When searching for `[]`, Rustdoc will return search results with either slices
168178
or arrays. If you know which one you want, you can force it to return results
@@ -182,6 +192,10 @@ results for types that match tuples, even though it also matches the type on
182192
its own. That is, `(u32)` matches `(u32,)` for the exact same reason that it
183193
also matches `Result<u32, Error>`.
184194

195+
The `->` operator has lower precedence than comma. If it's not wrapped
196+
in brackets, it delimits the return value for the function being searched for.
197+
To search for functions that take functions as parameters, use parenthesis.
198+
185199
### Limitations and quirks of type-based search
186200

187201
Type-based search is still a buggy, experimental, work-in-progress feature.
@@ -239,19 +253,21 @@ Item filters can be used in both name-based and type signature-based searches.
239253

240254
```text
241255
ident = *(ALPHA / DIGIT / "_")
242-
path = ident *(DOUBLE-COLON ident) [!]
256+
path = ident *(DOUBLE-COLON ident) [BANG]
243257
slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
244258
tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
245-
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / [!])
259+
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like)
246260
type-sep = COMMA/WS *(COMMA/WS)
247-
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
261+
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ]
248262
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
249-
generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
263+
normal-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
250264
CLOSE-ANGLE-BRACKET
265+
fn-like-generics = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN [ RETURN-ARROW arg ]
266+
generics = normal-generics / fn-like-generics
251267
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
252268
253269
exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
254-
type-search = [ nonempty-arg-list ] [ return-args ]
270+
type-search = [ nonempty-arg-list ]
255271
256272
query = *WS (exact-search / type-search) *WS
257273
@@ -296,6 +312,7 @@ QUOTE = %x22
296312
COMMA = ","
297313
RETURN-ARROW = "->"
298314
EQUAL = "="
315+
BANG = "!"
299316
300317
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
301318
DIGIT = %x30-39

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

+64-48
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,10 @@ function initSearch(rawSearchIndex) {
578578
// Syntactically, bindings are parsed as generics,
579579
// but the query engine treats them differently.
580580
if (gen.bindingName !== null) {
581-
bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]);
581+
if (gen.name !== null) {
582+
gen.bindingName.generics.unshift(gen);
583+
}
584+
bindings.set(gen.bindingName.name, gen.bindingName.generics);
582585
return false;
583586
}
584587
return true;
@@ -678,6 +681,38 @@ function initSearch(rawSearchIndex) {
678681
return end;
679682
}
680683

684+
function getFilteredNextElem(query, parserState, elems, isInGenerics) {
685+
const start = parserState.pos;
686+
if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
687+
throw ["Expected type filter before ", ":"];
688+
}
689+
getNextElem(query, parserState, elems, isInGenerics);
690+
if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
691+
if (parserState.typeFilter !== null) {
692+
throw [
693+
"Unexpected ",
694+
":",
695+
" (expected path after type filter ",
696+
parserState.typeFilter + ":",
697+
")",
698+
];
699+
}
700+
if (elems.length === 0) {
701+
throw ["Expected type filter before ", ":"];
702+
} else if (query.literalSearch) {
703+
throw ["Cannot use quotes on type filter"];
704+
}
705+
// The type filter doesn't count as an element since it's a modifier.
706+
const typeFilterElem = elems.pop();
707+
checkExtraTypeFilterCharacters(start, parserState);
708+
parserState.typeFilter = typeFilterElem.name;
709+
parserState.pos += 1;
710+
parserState.totalElems -= 1;
711+
query.literalSearch = false;
712+
getNextElem(query, parserState, elems, isInGenerics);
713+
}
714+
}
715+
681716
/**
682717
* @param {ParsedQuery} query
683718
* @param {ParserState} parserState
@@ -752,6 +787,32 @@ function initSearch(rawSearchIndex) {
752787
}
753788
parserState.pos += 1;
754789
getItemsBefore(query, parserState, generics, ">");
790+
} else if (parserState.pos < parserState.length &&
791+
parserState.userQuery[parserState.pos] === "("
792+
) {
793+
if (start >= end) {
794+
throw ["Found generics without a path"];
795+
}
796+
if (parserState.isInBinding) {
797+
throw ["Unexpected ", "(", " after ", "="];
798+
}
799+
parserState.pos += 1;
800+
const typeFilter = parserState.typeFilter;
801+
parserState.typeFilter = null;
802+
getItemsBefore(query, parserState, generics, ")");
803+
skipWhitespace(parserState);
804+
if (isReturnArrow(parserState)) {
805+
parserState.pos += 2;
806+
skipWhitespace(parserState);
807+
getFilteredNextElem(query, parserState, generics, isInGenerics);
808+
generics[generics.length - 1].bindingName = makePrimitiveElement("output");
809+
} else {
810+
generics.push(makePrimitiveElement(null, {
811+
bindingName: makePrimitiveElement("output"),
812+
typeFilter: null,
813+
}));
814+
}
815+
parserState.typeFilter = typeFilter;
755816
}
756817
if (isStringElem) {
757818
skipWhitespace(parserState);
@@ -811,7 +872,6 @@ function initSearch(rawSearchIndex) {
811872
function getItemsBefore(query, parserState, elems, endChar) {
812873
let foundStopChar = true;
813874
let foundSeparator = false;
814-
let start = parserState.pos;
815875

816876
// If this is a generic, keep the outer item's type filter around.
817877
const oldTypeFilter = parserState.typeFilter;
@@ -874,24 +934,6 @@ function initSearch(rawSearchIndex) {
874934
continue;
875935
} else if (c === ":" && isPathStart(parserState)) {
876936
throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
877-
} else if (c === ":") {
878-
if (parserState.typeFilter !== null) {
879-
throw ["Unexpected ", ":"];
880-
}
881-
if (elems.length === 0) {
882-
throw ["Expected type filter before ", ":"];
883-
} else if (query.literalSearch) {
884-
throw ["Cannot use quotes on type filter"];
885-
}
886-
// The type filter doesn't count as an element since it's a modifier.
887-
const typeFilterElem = elems.pop();
888-
checkExtraTypeFilterCharacters(start, parserState);
889-
parserState.typeFilter = typeFilterElem.name;
890-
parserState.pos += 1;
891-
parserState.totalElems -= 1;
892-
query.literalSearch = false;
893-
foundStopChar = true;
894-
continue;
895937
} else if (isEndCharacter(c)) {
896938
throw ["Unexpected ", c, " after ", extra];
897939
}
@@ -926,8 +968,7 @@ function initSearch(rawSearchIndex) {
926968
];
927969
}
928970
const posBefore = parserState.pos;
929-
start = parserState.pos;
930-
getNextElem(query, parserState, elems, endChar !== "");
971+
getFilteredNextElem(query, parserState, elems, endChar !== "");
931972
if (endChar !== "" && parserState.pos >= parserState.length) {
932973
throw ["Unclosed ", extra];
933974
}
@@ -1004,7 +1045,6 @@ function initSearch(rawSearchIndex) {
10041045
*/
10051046
function parseInput(query, parserState) {
10061047
let foundStopChar = true;
1007-
let start = parserState.pos;
10081048

10091049
while (parserState.pos < parserState.length) {
10101050
const c = parserState.userQuery[parserState.pos];
@@ -1022,29 +1062,6 @@ function initSearch(rawSearchIndex) {
10221062
throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]];
10231063
}
10241064
throw ["Unexpected ", c];
1025-
} else if (c === ":" && !isPathStart(parserState)) {
1026-
if (parserState.typeFilter !== null) {
1027-
throw [
1028-
"Unexpected ",
1029-
":",
1030-
" (expected path after type filter ",
1031-
parserState.typeFilter + ":",
1032-
")",
1033-
];
1034-
} else if (query.elems.length === 0) {
1035-
throw ["Expected type filter before ", ":"];
1036-
} else if (query.literalSearch) {
1037-
throw ["Cannot use quotes on type filter"];
1038-
}
1039-
// The type filter doesn't count as an element since it's a modifier.
1040-
const typeFilterElem = query.elems.pop();
1041-
checkExtraTypeFilterCharacters(start, parserState);
1042-
parserState.typeFilter = typeFilterElem.name;
1043-
parserState.pos += 1;
1044-
parserState.totalElems -= 1;
1045-
query.literalSearch = false;
1046-
foundStopChar = true;
1047-
continue;
10481065
} else if (c === " ") {
10491066
skipWhitespace(parserState);
10501067
continue;
@@ -1080,8 +1097,7 @@ function initSearch(rawSearchIndex) {
10801097
];
10811098
}
10821099
const before = query.elems.length;
1083-
start = parserState.pos;
1084-
getNextElem(query, parserState, query.elems, false);
1100+
getFilteredNextElem(query, parserState, query.elems, false);
10851101
if (query.elems.length === before) {
10861102
// Nothing was added, weird... Let's increase the position to not remain stuck.
10871103
parserState.pos += 1;

tests/rustdoc-js-std/parser-errors.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const PARSED = [
195195
original: "a (b:",
196196
returned: [],
197197
userQuery: "a (b:",
198-
error: "Expected `,`, `:` or `->`, found `(`",
198+
error: "Unclosed `(`",
199199
},
200200
{
201201
query: "_:",
@@ -357,7 +357,16 @@ const PARSED = [
357357
original: "a,:",
358358
returned: [],
359359
userQuery: "a,:",
360-
error: 'Unexpected `,` in type filter (before `:`)',
360+
error: 'Expected type filter before `:`',
361+
},
362+
{
363+
query: "a!:",
364+
elems: [],
365+
foundElems: 0,
366+
original: "a!:",
367+
returned: [],
368+
userQuery: "a!:",
369+
error: 'Unexpected `!` in type filter (before `:`)',
361370
},
362371
{
363372
query: " a<> :",
@@ -366,7 +375,7 @@ const PARSED = [
366375
original: "a<> :",
367376
returned: [],
368377
userQuery: "a<> :",
369-
error: 'Unexpected `<` in type filter (before `:`)',
378+
error: 'Expected `,`, `:` or `->` after `>`, found `:`',
370379
},
371380
{
372381
query: "mod : :",

0 commit comments

Comments
 (0)