Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 84d7a2c

Browse files
committedJan 7, 2024
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 84d7a2c

File tree

6 files changed

+529
-81
lines changed

6 files changed

+529
-81
lines changed
 

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

Lines changed: 30 additions & 16 deletions
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.
@@ -220,9 +234,6 @@ Most of these limitations should be addressed in future version of Rustdoc.
220234

221235
* Searching for lifetimes is not supported.
222236

223-
* It's impossible to search for closures based on their parameters or
224-
return values.
225-
226237
* It's impossible to search based on the length of an array.
227238

228239
## Item filtering
@@ -239,19 +250,21 @@ Item filters can be used in both name-based and type signature-based searches.
239250

240251
```text
241252
ident = *(ALPHA / DIGIT / "_")
242-
path = ident *(DOUBLE-COLON ident) [!]
253+
path = ident *(DOUBLE-COLON ident) [BANG]
243254
slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
244255
tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
245-
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / [!])
256+
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like)
246257
type-sep = COMMA/WS *(COMMA/WS)
247-
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
258+
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ]
248259
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)
260+
normal-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
250261
CLOSE-ANGLE-BRACKET
262+
fn-like-generics = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN [ RETURN-ARROW arg ]
263+
generics = normal-generics / fn-like-generics
251264
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
252265
253266
exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
254-
type-search = [ nonempty-arg-list ] [ return-args ]
267+
type-search = [ nonempty-arg-list ]
255268
256269
query = *WS (exact-search / type-search) *WS
257270
@@ -296,6 +309,7 @@ QUOTE = %x22
296309
COMMA = ","
297310
RETURN-ARROW = "->"
298311
EQUAL = "="
312+
BANG = "!"
299313
300314
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
301315
DIGIT = %x30-39

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

Lines changed: 64 additions & 48 deletions
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

Lines changed: 12 additions & 3 deletions
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 : :",

‎tests/rustdoc-js-std/parser-hof.js

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const PARSED = [
2+
// ML-style HOF
23
{
34
query: "(-> F<P>)",
45
elems: [{
@@ -373,4 +374,339 @@ const PARSED = [
373374
userQuery: "x, trait:(aaaaa, b -> a)",
374375
error: null,
375376
},
377+
// Rust-style HOF
378+
{
379+
query: "Fn () -> F<P>",
380+
elems: [{
381+
name: "fn",
382+
fullPath: ["fn"],
383+
pathWithoutLast: [],
384+
pathLast: "fn",
385+
generics: [],
386+
bindings: [
387+
[
388+
"output",
389+
[{
390+
name: "f",
391+
fullPath: ["f"],
392+
pathWithoutLast: [],
393+
pathLast: "f",
394+
generics: [
395+
{
396+
name: "p",
397+
fullPath: ["p"],
398+
pathWithoutLast: [],
399+
pathLast: "p",
400+
generics: [],
401+
},
402+
],
403+
typeFilter: -1,
404+
}],
405+
],
406+
],
407+
typeFilter: -1,
408+
}],
409+
foundElems: 1,
410+
original: "Fn () -> F<P>",
411+
returned: [],
412+
userQuery: "fn () -> f<p>",
413+
error: null,
414+
},
415+
{
416+
query: "FnMut() -> P",
417+
elems: [{
418+
name: "fnmut",
419+
fullPath: ["fnmut"],
420+
pathWithoutLast: [],
421+
pathLast: "fnmut",
422+
generics: [],
423+
bindings: [
424+
[
425+
"output",
426+
[{
427+
name: "p",
428+
fullPath: ["p"],
429+
pathWithoutLast: [],
430+
pathLast: "p",
431+
generics: [],
432+
typeFilter: -1,
433+
}],
434+
],
435+
],
436+
typeFilter: -1,
437+
}],
438+
foundElems: 1,
439+
original: "FnMut() -> P",
440+
returned: [],
441+
userQuery: "fnmut() -> p",
442+
error: null,
443+
},
444+
{
445+
query: "(FnMut() -> P)",
446+
elems: [{
447+
name: "fnmut",
448+
fullPath: ["fnmut"],
449+
pathWithoutLast: [],
450+
pathLast: "fnmut",
451+
generics: [],
452+
bindings: [
453+
[
454+
"output",
455+
[{
456+
name: "p",
457+
fullPath: ["p"],
458+
pathWithoutLast: [],
459+
pathLast: "p",
460+
generics: [],
461+
typeFilter: -1,
462+
}],
463+
],
464+
],
465+
typeFilter: -1,
466+
}],
467+
foundElems: 1,
468+
original: "(FnMut() -> P)",
469+
returned: [],
470+
userQuery: "(fnmut() -> p)",
471+
error: null,
472+
},
473+
{
474+
query: "Fn(F<P>)",
475+
elems: [{
476+
name: "fn",
477+
fullPath: ["fn"],
478+
pathWithoutLast: [],
479+
pathLast: "fn",
480+
generics: [{
481+
name: "f",
482+
fullPath: ["f"],
483+
pathWithoutLast: [],
484+
pathLast: "f",
485+
generics: [
486+
{
487+
name: "p",
488+
fullPath: ["p"],
489+
pathWithoutLast: [],
490+
pathLast: "p",
491+
generics: [],
492+
},
493+
],
494+
typeFilter: -1,
495+
}],
496+
bindings: [
497+
[
498+
"output",
499+
[],
500+
],
501+
],
502+
typeFilter: -1,
503+
}],
504+
foundElems: 1,
505+
original: "Fn(F<P>)",
506+
returned: [],
507+
userQuery: "fn(f<p>)",
508+
error: null,
509+
},
510+
{
511+
query: "primitive:fnonce(aaaaa, b) -> a",
512+
elems: [{
513+
name: "fnonce",
514+
fullPath: ["fnonce"],
515+
pathWithoutLast: [],
516+
pathLast: "fnonce",
517+
generics: [
518+
{
519+
name: "aaaaa",
520+
fullPath: ["aaaaa"],
521+
pathWithoutLast: [],
522+
pathLast: "aaaaa",
523+
generics: [],
524+
typeFilter: -1,
525+
},
526+
{
527+
name: "b",
528+
fullPath: ["b"],
529+
pathWithoutLast: [],
530+
pathLast: "b",
531+
generics: [],
532+
typeFilter: -1,
533+
},
534+
],
535+
bindings: [
536+
[
537+
"output",
538+
[{
539+
name: "a",
540+
fullPath: ["a"],
541+
pathWithoutLast: [],
542+
pathLast: "a",
543+
generics: [],
544+
typeFilter: -1,
545+
}],
546+
],
547+
],
548+
typeFilter: 1,
549+
}],
550+
foundElems: 1,
551+
original: "primitive:fnonce(aaaaa, b) -> a",
552+
returned: [],
553+
userQuery: "primitive:fnonce(aaaaa, b) -> a",
554+
error: null,
555+
},
556+
{
557+
query: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
558+
elems: [{
559+
name: "fnonce",
560+
fullPath: ["fnonce"],
561+
pathWithoutLast: [],
562+
pathLast: "fnonce",
563+
generics: [
564+
{
565+
name: "aaaaa",
566+
fullPath: ["aaaaa"],
567+
pathWithoutLast: [],
568+
pathLast: "aaaaa",
569+
generics: [],
570+
typeFilter: -1,
571+
},
572+
{
573+
name: "b",
574+
fullPath: ["b"],
575+
pathWithoutLast: [],
576+
pathLast: "b",
577+
generics: [],
578+
typeFilter: 0,
579+
},
580+
],
581+
bindings: [
582+
[
583+
"output",
584+
[{
585+
name: "a",
586+
fullPath: ["a"],
587+
pathWithoutLast: [],
588+
pathLast: "a",
589+
generics: [],
590+
typeFilter: 10,
591+
}],
592+
],
593+
],
594+
typeFilter: 1,
595+
}],
596+
foundElems: 1,
597+
original: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
598+
returned: [],
599+
userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
600+
error: null,
601+
},
602+
{
603+
query: "x, trait:fn(aaaaa, b -> a)",
604+
elems: [
605+
{
606+
name: "x",
607+
fullPath: ["x"],
608+
pathWithoutLast: [],
609+
pathLast: "x",
610+
generics: [],
611+
typeFilter: -1,
612+
},
613+
{
614+
name: "fn",
615+
fullPath: ["fn"],
616+
pathWithoutLast: [],
617+
pathLast: "fn",
618+
generics: [
619+
{
620+
name: "->",
621+
fullPath: ["->"],
622+
pathWithoutLast: [],
623+
pathLast: "->",
624+
generics: [
625+
{
626+
name: "aaaaa",
627+
fullPath: ["aaaaa"],
628+
pathWithoutLast: [],
629+
pathLast: "aaaaa",
630+
generics: [],
631+
typeFilter: -1,
632+
},
633+
{
634+
name: "b",
635+
fullPath: ["b"],
636+
pathWithoutLast: [],
637+
pathLast: "b",
638+
generics: [],
639+
typeFilter: -1,
640+
},
641+
],
642+
bindings: [
643+
[
644+
"output",
645+
[{
646+
name: "a",
647+
fullPath: ["a"],
648+
pathWithoutLast: [],
649+
pathLast: "a",
650+
generics: [],
651+
typeFilter: -1,
652+
}],
653+
],
654+
],
655+
typeFilter: -1,
656+
},
657+
],
658+
bindings: [
659+
[
660+
"output",
661+
[],
662+
]
663+
],
664+
typeFilter: 10,
665+
}
666+
],
667+
foundElems: 2,
668+
original: "x, trait:fn(aaaaa, b -> a)",
669+
returned: [],
670+
userQuery: "x, trait:fn(aaaaa, b -> a)",
671+
error: null,
672+
},
673+
{
674+
query: 'a,b(c)',
675+
elems: [
676+
{
677+
name: "a",
678+
fullPath: ["a"],
679+
pathWithoutLast: [],
680+
pathLast: "a",
681+
generics: [],
682+
typeFilter: -1,
683+
},
684+
{
685+
name: "b",
686+
fullPath: ["b"],
687+
pathWithoutLast: [],
688+
pathLast: "b",
689+
generics: [{
690+
name: "c",
691+
fullPath: ["c"],
692+
pathWithoutLast: [],
693+
pathLast: "c",
694+
generics: [],
695+
typeFilter: -1,
696+
}],
697+
bindings: [
698+
[
699+
"output",
700+
[],
701+
]
702+
],
703+
typeFilter: -1,
704+
}
705+
],
706+
foundElems: 2,
707+
original: "a,b(c)",
708+
returned: [],
709+
userQuery: "a,b(c)",
710+
error: null,
711+
},
376712
];

‎tests/rustdoc-js-std/parser-weird-queries.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@ const PARSED = [
3737
userQuery: "a b",
3838
error: null,
3939
},
40-
{
41-
query: 'a,b(c)',
42-
elems: [],
43-
foundElems: 0,
44-
original: "a,b(c)",
45-
returned: [],
46-
userQuery: "a,b(c)",
47-
error: "Expected `,`, `:` or `->`, found `(`",
48-
},
4940
{
5041
query: 'aaa,a',
5142
elems: [

‎tests/rustdoc-js/hof.js

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
// exact-check
22

33
const EXPECTED = [
4+
// not a HOF query
5+
{
6+
'query': 'u32 -> !',
7+
'others': [],
8+
},
9+
410
// ML-style higher-order function notation
511
{
612
'query': 'bool, (u32 -> !) -> ()',
@@ -53,11 +59,6 @@ const EXPECTED = [
5359
{"path": "hof", "name": "fn_once"},
5460
],
5561
},
56-
{
57-
'query': 'u32 -> !',
58-
// not a HOF query
59-
'others': [],
60-
},
6162
{
6263
'query': '(str, str -> i8) -> ()',
6364
'others': [
@@ -91,4 +92,85 @@ const EXPECTED = [
9192
// params and return are not the same
9293
'others': [],
9394
},
95+
96+
// Rust-style higher-order function notation
97+
{
98+
'query': 'bool, fn(u32) -> ! -> ()',
99+
'others': [
100+
{"path": "hof", "name": "fn_ptr"},
101+
],
102+
},
103+
{
104+
'query': 'u8, fnonce(u32) -> ! -> ()',
105+
'others': [
106+
{"path": "hof", "name": "fn_once"},
107+
],
108+
},
109+
{
110+
'query': 'u8, fn(u32) -> ! -> ()',
111+
// fnonce != fn
112+
'others': [],
113+
},
114+
{
115+
'query': 'i8, fnmut(u32) -> ! -> ()',
116+
'others': [
117+
{"path": "hof", "name": "fn_mut"},
118+
],
119+
},
120+
{
121+
'query': 'i8, fn(u32) -> ! -> ()',
122+
// fnmut != fn
123+
'others': [],
124+
},
125+
{
126+
'query': 'char, fn(u32) -> ! -> ()',
127+
'others': [
128+
{"path": "hof", "name": "fn_"},
129+
],
130+
},
131+
{
132+
'query': 'char, fnmut(u32) -> ! -> ()',
133+
// fn != fnmut
134+
'others': [],
135+
},
136+
{
137+
'query': 'fn(first<u32>) -> ! -> ()',
138+
'others': [
139+
{"path": "hof", "name": "fn_ptr"},
140+
],
141+
},
142+
{
143+
'query': 'fnonce(second<u32>) -> ! -> ()',
144+
'others': [
145+
{"path": "hof", "name": "fn_once"},
146+
],
147+
},
148+
{
149+
'query': 'fnmut(third<u32>) -> ! -> ()',
150+
'others': [
151+
{"path": "hof", "name": "fn_mut"},
152+
],
153+
},
154+
{
155+
'query': 'fn(u32) -> ! -> ()',
156+
'others': [
157+
// fn matches primitive:fn and trait:Fn
158+
{"path": "hof", "name": "fn_"},
159+
{"path": "hof", "name": "fn_ptr"},
160+
],
161+
},
162+
{
163+
'query': 'trait:fn(u32) -> ! -> ()',
164+
'others': [
165+
// fn matches primitive:fn and trait:Fn
166+
{"path": "hof", "name": "fn_"},
167+
],
168+
},
169+
{
170+
'query': 'primitive:fn(u32) -> ! -> ()',
171+
'others': [
172+
// fn matches primitive:fn and trait:Fn
173+
{"path": "hof", "name": "fn_ptr"},
174+
],
175+
},
94176
];

0 commit comments

Comments
 (0)
Please sign in to comment.