Rollup merge of #119676 - notriddle:notriddle/rustdoc-search-hof, r=GuillaumeGomez
rustdoc-search: search types by higher-order functions This feature extends rustdoc with syntax and search index information for searching function pointers and closures (Higher-Order Functions, or HOF). Part of https://github.com/rust-lang/rust/issues/60485 This PR adds two syntaxes: a high-level one for finding any kind of HOF, and a direct implementation of the parenthesized path syntax that Rust itself uses. ## Preview pages | Query | Results | |-------|---------| | [`option<T>, (fnonce (T) -> bool) -> option<T>`][optionfilter] | `Option::filter` | | [`option<T>, (T -> bool) -> option<T>`][optionfilter2] | `Option::filter` | Updated chapter of the book: https://notriddle.com/rustdoc-html-demo-9/search-hof/rustdoc/read-documentation/search.html [optionfilter]: https://notriddle.com/rustdoc-html-demo-9/search-hof/std/vec/struct.Vec.html?search=option<T>%2C+(fnonce+(T)+->+bool)+->+option<T>&filter-crate=std [optionfilter2]: https://notriddle.com/rustdoc-html-demo-9/search-hof/std/vec/struct.Vec.html?search=option<T>%2C+(T+->+bool)+->+option<T>&filter-crate=std ## Motivation When type-based search was first landed, it was directly [described as incomplete][a comment]. [a comment]: https://github.com/rust-lang/rust/pull/23289#issuecomment-79437386 Filling out the missing functionality is going to mean adding support for more of Rust's [type expression] syntax, such as references, raw pointers, function pointers, and closures. This PR adds function pointers and closures. [type expression]: https://doc.rust-lang.org/reference/types.html#type-expressions There's been demand for something "like Hoogle, but for Rust" expressed a few times [1](https://www.reddit.com/r/rust/comments/y8sbid/is_there_a_website_like_haskells_hoogle_for_rust/) [2](https://users.rust-lang.org/t/rust-equivalent-of-haskells-hoogle/102280) [3](https://internals.rust-lang.org/t/std-library-inclusion-policy/6852/2) [4](https://discord.com/channels/442252698964721669/448238009733742612/1109502307495858216). Some of them just don't realize what functionality already exists ([`Duration -> u64`](https://doc.rust-lang.org/nightly/std/?search=duration%20-%3E%20u64) already works), but a lot of them specifically want to search for higher-order functions like option combinators. ## Guide-level explanation (from the Rustdoc book) To search for a function that accepts a function as a parameter, like `Iterator::all`, wrap the nested signature in parenthesis, as in [`Iterator<T>, (T -> bool) -> bool`][iterator-all]. You can also search for a specific closure trait, such as `Iterator<T>, (FnMut(T) -> bool) -> bool`, but you need to know which one you want. [iterator-all]: https://notriddle.com/rustdoc-html-demo-9/search-hof/std/vec/struct.Vec.html?search=Iterator<T>%2C+(T+->+bool)+->+bool&filter-crate=std ## Reference-level description (also from the Rustdoc book) ### Primitives with Special Syntax <table> <thead> <tr> <th>Shorthand</th> <th>Explicit names</th> </tr> </thead> <tbody> <tr><td colspan="2">Before this PR</td></tr> <tr> <td><code>[]</code></td> <td><code>primitive:slice</code> and/or <code>primitive:array</code></td> </tr> <tr> <td><code>[T]</code></td> <td><code>primitive:slice<T></code> and/or <code>primitive:array<T></code></td> </tr> <tr> <td><code>!</code></td> <td><code>primitive:never</code></td> </tr> <tr> <td><code>()</code></td> <td><code>primitive:unit</code> and/or <code>primitive:tuple</code></td> </tr> <tr> <td><code>(T)</code></td> <td><code>T</code></td> </tr> <tr> <td><code>(T,)</code></td> <td><code>primitive:tuple<T></code></td> </tr> <tr><td colspan="2">After this PR</td></tr> <tr> <td><code>(T, U -> V, W)</code></td> <td><code>fn(T, U) -> (V, W)</code>, Fn, FnMut, and FnOnce</td> </tr> </tbody> </table> The `->` operator has lower precedence than comma. If it's not wrapped in brackets, it delimits the return value for the function being searched for. To search for functions that take functions as parameters, use parenthesis. ### Search query grammar ```ebnf ident = *(ALPHA / DIGIT / "_") path = ident *(DOUBLE-COLON ident) [BANG] slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like) type-sep = COMMA/WS *(COMMA/WS) nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ] generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep) normal-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep) CLOSE-ANGLE-BRACKET fn-like-generics = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN [ RETURN-ARROW arg ] generics = normal-generics / fn-like-generics return-args = RETURN-ARROW *(type-sep) nonempty-arg-list exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ] type-search = [ nonempty-arg-list ] query = *WS (exact-search / type-search) *WS ; unchanged parts of the grammar, like the full list of type filters, are omitted ``` ## Future direction ### The remaining type expression grammar As described in https://github.com/rust-lang/rust/pull/118194, this is another step in the type expression grammar: BareFunction, and the function-like mode of TypePath, are now supported. * RawPointerType and ReferenceType actually are a priority. * ImplTraitType and TraitObjectType (and ImplTraitTypeOneBound and TraitObjectTypeOneBound) aren't as much of a priority, since they desugar pretty easily. ### Search subtyping and traits This is the other major factor that makes it less useful than it should be. * `iterator<result<t>> -> result<t>` doesn't find `Result::from_iter`. You have to search [`intoiterator<result<t>> -> result<t>`](https://notriddle.com/rustdoc-html-demo-9/search-hof/std/vec/struct.Vec.html?search=intoiterator%3Cresult%3Ct%3E%3E%20-%3E%20result%3Ct%3E&filter-crate=std). Nobody's going to search for IntoIterator unless they basically already know about it and don't need the search engine anyway. * Iterator combinators are usually structs that happen to implement Iterator, like `std::iter::Map`. To solve these cases, it needs to look at trait implementations, knowing that Iterator is a "subtype of" IntoIterator, and Map is a "subtype of" Iterator, so `iterator -> result` is a subtype of `intoiterator -> result` and `iterator<t>, (t -> u) -> iterator<u>` is a subtype of [`iterator<t>, (t -> u) -> map<t -> u>`](https://notriddle.com/rustdoc-html-demo-9/search-hof/std/vec/struct.Vec.html?search=iterator%3Ct%3E%2C%20(t%20-%3E%20u)%20-%3E%20map%3Ct%20-%3E%20u%3E&filter-crate=std).
This commit is contained in:
commit
6694918344
@ -63,11 +63,12 @@ Before describing the syntax in more detail, here's a few sample searches of
|
||||
the standard library and functions that are included in the results list:
|
||||
|
||||
| Query | Results |
|
||||
|-------|--------|
|
||||
|-------|---------|
|
||||
| [`usize -> vec`][] | `slice::repeat` and `Vec::with_capacity` |
|
||||
| [`vec, vec -> bool`][] | `Vec::eq` |
|
||||
| [`option<T>, fnonce -> option<U>`][] | `Option::map` and `Option::and_then` |
|
||||
| [`option<T>, fnonce -> option<T>`][] | `Option::filter` and `Option::inspect` |
|
||||
| [`option<T>, (fnonce (T) -> bool) -> option<T>`][optionfilter] | `Option::filter` |
|
||||
| [`option<T>, (T -> bool) -> option<T>`][optionfilter2] | `Option::filter` |
|
||||
| [`option -> default`][] | `Option::unwrap_or_default` |
|
||||
| [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
|
||||
| [`any -> !`][] | `panic::panic_any` |
|
||||
@ -77,7 +78,8 @@ the standard library and functions that are included in the results list:
|
||||
[`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std
|
||||
[`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std
|
||||
[`option<T>, fnonce -> option<U>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<U>&filter-crate=std
|
||||
[`option<T>, fnonce -> option<T>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<T>&filter-crate=std
|
||||
[optionfilter]: ../../std/vec/struct.Vec.html?search=option<T>%2C+(fnonce+(T)+->+bool)+->+option<T>&filter-crate=std
|
||||
[optionfilter2]: ../../std/vec/struct.Vec.html?search=option<T>%2C+(T+->+bool)+->+option<T>&filter-crate=std
|
||||
[`option -> default`]: ../../std/vec/struct.Vec.html?search=option%20-%3E%20default&filter-crate=std
|
||||
[`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std
|
||||
[stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std
|
||||
@ -151,16 +153,26 @@ will match these queries:
|
||||
|
||||
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
|
||||
|
||||
To search for a function that accepts a function as a parameter,
|
||||
like `Iterator::all`, wrap the nested signature in parenthesis,
|
||||
as in [`Iterator<T>, (T -> bool) -> bool`][iterator-all].
|
||||
You can also search for a specific closure trait,
|
||||
such as `Iterator<T>, (FnMut(T) -> bool) -> bool`,
|
||||
but you need to know which one you want.
|
||||
|
||||
[iterator-all]: ../../std/vec/struct.Vec.html?search=Iterator<T>%2C+(T+->+bool)+->+bool&filter-crate=std
|
||||
|
||||
### Primitives with Special Syntax
|
||||
|
||||
| Shorthand | Explicit names |
|
||||
| --------- | ------------------------------------------------ |
|
||||
| `[]` | `primitive:slice` and/or `primitive:array` |
|
||||
| `[T]` | `primitive:slice<T>` and/or `primitive:array<T>` |
|
||||
| `()` | `primitive:unit` and/or `primitive:tuple` |
|
||||
| `(T)` | `T` |
|
||||
| `(T,)` | `primitive:tuple<T>` |
|
||||
| `!` | `primitive:never` |
|
||||
| Shorthand | Explicit names |
|
||||
| ---------------- | ------------------------------------------------- |
|
||||
| `[]` | `primitive:slice` and/or `primitive:array` |
|
||||
| `[T]` | `primitive:slice<T>` and/or `primitive:array<T>` |
|
||||
| `()` | `primitive:unit` and/or `primitive:tuple` |
|
||||
| `(T)` | `T` |
|
||||
| `(T,)` | `primitive:tuple<T>` |
|
||||
| `!` | `primitive:never` |
|
||||
| `(T, U -> V, W)` | `fn(T, U) -> (V, W)`, `Fn`, `FnMut`, and `FnOnce` |
|
||||
|
||||
When searching for `[]`, Rustdoc will return search results with either slices
|
||||
or arrays. If you know which one you want, you can force it to return results
|
||||
@ -180,6 +192,10 @@ results for types that match tuples, even though it also matches the type on
|
||||
its own. That is, `(u32)` matches `(u32,)` for the exact same reason that it
|
||||
also matches `Result<u32, Error>`.
|
||||
|
||||
The `->` operator has lower precedence than comma. If it's not wrapped
|
||||
in brackets, it delimits the return value for the function being searched for.
|
||||
To search for functions that take functions as parameters, use parenthesis.
|
||||
|
||||
### Limitations and quirks of type-based search
|
||||
|
||||
Type-based search is still a buggy, experimental, work-in-progress feature.
|
||||
@ -218,9 +234,6 @@ Most of these limitations should be addressed in future version of Rustdoc.
|
||||
|
||||
* Searching for lifetimes is not supported.
|
||||
|
||||
* It's impossible to search for closures based on their parameters or
|
||||
return values.
|
||||
|
||||
* It's impossible to search based on the length of an array.
|
||||
|
||||
## Item filtering
|
||||
@ -237,19 +250,21 @@ Item filters can be used in both name-based and type signature-based searches.
|
||||
|
||||
```text
|
||||
ident = *(ALPHA / DIGIT / "_")
|
||||
path = ident *(DOUBLE-COLON ident) [!]
|
||||
path = ident *(DOUBLE-COLON ident) [BANG]
|
||||
slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
|
||||
tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
|
||||
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / [!])
|
||||
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like)
|
||||
type-sep = COMMA/WS *(COMMA/WS)
|
||||
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
|
||||
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ]
|
||||
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
|
||||
generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
|
||||
normal-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
|
||||
CLOSE-ANGLE-BRACKET
|
||||
fn-like-generics = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN [ RETURN-ARROW arg ]
|
||||
generics = normal-generics / fn-like-generics
|
||||
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
|
||||
|
||||
exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
|
||||
type-search = [ nonempty-arg-list ] [ return-args ]
|
||||
type-search = [ nonempty-arg-list ]
|
||||
|
||||
query = *WS (exact-search / type-search) *WS
|
||||
|
||||
@ -294,6 +309,7 @@ QUOTE = %x22
|
||||
COMMA = ","
|
||||
RETURN-ARROW = "->"
|
||||
EQUAL = "="
|
||||
BANG = "!"
|
||||
|
||||
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
|
||||
DIGIT = %x30-39
|
||||
|
@ -4,6 +4,7 @@ use std::collections::{BTreeMap, VecDeque};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
|
||||
use thin_vec::ThinVec;
|
||||
@ -566,6 +567,7 @@ fn get_index_type_id(
|
||||
// The type parameters are converted to generics in `simplify_fn_type`
|
||||
clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
|
||||
clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
|
||||
clean::BareFunction(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Fn)),
|
||||
clean::Tuple(ref n) if n.is_empty() => {
|
||||
Some(RenderTypeId::Primitive(clean::PrimitiveType::Unit))
|
||||
}
|
||||
@ -584,7 +586,7 @@ fn get_index_type_id(
|
||||
}
|
||||
}
|
||||
// Not supported yet
|
||||
clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
|
||||
clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -785,6 +787,42 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
);
|
||||
}
|
||||
res.push(get_index_type(arg, ty_generics, rgen));
|
||||
} else if let Type::BareFunction(ref bf) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
for ty in bf.decl.inputs.values.iter().map(|arg| &arg.type_) {
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
// The search index, for simplicity's sake, represents fn pointers and closures
|
||||
// the same way: as a tuple for the parameters, and an associated type for the
|
||||
// return type.
|
||||
let mut ty_output = Vec::new();
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&bf.decl.output,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_output,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
let ty_bindings = vec![(RenderTypeId::AssociatedType(sym::Output), ty_output)];
|
||||
res.push(RenderType {
|
||||
id: get_index_type_id(&arg, rgen),
|
||||
bindings: Some(ty_bindings),
|
||||
generics: Some(ty_generics),
|
||||
});
|
||||
} else {
|
||||
// This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're
|
||||
// looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't.
|
||||
|
@ -1,3 +1,4 @@
|
||||
// ignore-tidy-filelength
|
||||
/* global addClass, getNakedUrl, getSettingValue */
|
||||
/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
|
||||
|
||||
@ -245,33 +246,49 @@ function initSearch(rawSearchIndex) {
|
||||
*
|
||||
* @type {Map<string, {id: integer, assocOnly: boolean}>}
|
||||
*/
|
||||
let typeNameIdMap;
|
||||
const typeNameIdMap = new Map();
|
||||
const ALIASES = new Map();
|
||||
|
||||
/**
|
||||
* Special type name IDs for searching by array.
|
||||
*/
|
||||
let typeNameIdOfArray;
|
||||
const typeNameIdOfArray = buildTypeMapIndex("array");
|
||||
/**
|
||||
* Special type name IDs for searching by slice.
|
||||
*/
|
||||
let typeNameIdOfSlice;
|
||||
const typeNameIdOfSlice = buildTypeMapIndex("slice");
|
||||
/**
|
||||
* Special type name IDs for searching by both array and slice (`[]` syntax).
|
||||
*/
|
||||
let typeNameIdOfArrayOrSlice;
|
||||
const typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
|
||||
/**
|
||||
* Special type name IDs for searching by tuple.
|
||||
*/
|
||||
let typeNameIdOfTuple;
|
||||
const typeNameIdOfTuple = buildTypeMapIndex("tuple");
|
||||
/**
|
||||
* Special type name IDs for searching by unit.
|
||||
*/
|
||||
let typeNameIdOfUnit;
|
||||
const typeNameIdOfUnit = buildTypeMapIndex("unit");
|
||||
/**
|
||||
* Special type name IDs for searching by both tuple and unit (`()` syntax).
|
||||
*/
|
||||
let typeNameIdOfTupleOrUnit;
|
||||
const typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
|
||||
/**
|
||||
* Special type name IDs for searching `fn`.
|
||||
*/
|
||||
const typeNameIdOfFn = buildTypeMapIndex("fn");
|
||||
/**
|
||||
* Special type name IDs for searching `fnmut`.
|
||||
*/
|
||||
const typeNameIdOfFnMut = buildTypeMapIndex("fnmut");
|
||||
/**
|
||||
* Special type name IDs for searching `fnonce`.
|
||||
*/
|
||||
const typeNameIdOfFnOnce = buildTypeMapIndex("fnonce");
|
||||
/**
|
||||
* Special type name IDs for searching higher order functions (`->` syntax).
|
||||
*/
|
||||
const typeNameIdOfHof = buildTypeMapIndex("->");
|
||||
|
||||
/**
|
||||
* Add an item to the type Name->ID map, or, if one already exists, use it.
|
||||
@ -464,6 +481,21 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
function makePrimitiveElement(name, extra) {
|
||||
return Object.assign({
|
||||
name,
|
||||
id: null,
|
||||
fullPath: [name],
|
||||
pathWithoutLast: [],
|
||||
pathLast: name,
|
||||
normalizedPathLast: name,
|
||||
generics: [],
|
||||
bindings: new Map(),
|
||||
typeFilter: "primitive",
|
||||
bindingName: null,
|
||||
}, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ParsedQuery} query
|
||||
* @param {ParserState} parserState
|
||||
@ -501,18 +533,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
const bindingName = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
return {
|
||||
name: "never",
|
||||
id: null,
|
||||
fullPath: ["never"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "never",
|
||||
normalizedPathLast: "never",
|
||||
generics: [],
|
||||
bindings: new Map(),
|
||||
typeFilter: "primitive",
|
||||
bindingName,
|
||||
};
|
||||
return makePrimitiveElement("never", { bindingName });
|
||||
}
|
||||
const quadcolon = /::\s*::/.exec(path);
|
||||
if (path.startsWith("::")) {
|
||||
@ -558,7 +579,10 @@ function initSearch(rawSearchIndex) {
|
||||
// Syntactically, bindings are parsed as generics,
|
||||
// but the query engine treats them differently.
|
||||
if (gen.bindingName !== null) {
|
||||
bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]);
|
||||
if (gen.name !== null) {
|
||||
gen.bindingName.generics.unshift(gen);
|
||||
}
|
||||
bindings.set(gen.bindingName.name, gen.bindingName.generics);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -658,6 +682,38 @@ function initSearch(rawSearchIndex) {
|
||||
return end;
|
||||
}
|
||||
|
||||
function getFilteredNextElem(query, parserState, elems, isInGenerics) {
|
||||
const start = parserState.pos;
|
||||
if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
|
||||
throw ["Expected type filter before ", ":"];
|
||||
}
|
||||
getNextElem(query, parserState, elems, isInGenerics);
|
||||
if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
|
||||
if (parserState.typeFilter !== null) {
|
||||
throw [
|
||||
"Unexpected ",
|
||||
":",
|
||||
" (expected path after type filter ",
|
||||
parserState.typeFilter + ":",
|
||||
")",
|
||||
];
|
||||
}
|
||||
if (elems.length === 0) {
|
||||
throw ["Expected type filter before ", ":"];
|
||||
} else if (query.literalSearch) {
|
||||
throw ["Cannot use quotes on type filter"];
|
||||
}
|
||||
// The type filter doesn't count as an element since it's a modifier.
|
||||
const typeFilterElem = elems.pop();
|
||||
checkExtraTypeFilterCharacters(start, parserState);
|
||||
parserState.typeFilter = typeFilterElem.name;
|
||||
parserState.pos += 1;
|
||||
parserState.totalElems -= 1;
|
||||
query.literalSearch = false;
|
||||
getNextElem(query, parserState, elems, isInGenerics);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ParsedQuery} query
|
||||
* @param {ParserState} parserState
|
||||
@ -671,28 +727,19 @@ function initSearch(rawSearchIndex) {
|
||||
let start = parserState.pos;
|
||||
let end;
|
||||
if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {
|
||||
let endChar = ")";
|
||||
let name = "()";
|
||||
let friendlyName = "tuple";
|
||||
let endChar = ")";
|
||||
let name = "()";
|
||||
let friendlyName = "tuple";
|
||||
|
||||
if (parserState.userQuery[parserState.pos] === "[") {
|
||||
endChar = "]";
|
||||
name = "[]";
|
||||
friendlyName = "slice";
|
||||
}
|
||||
if (parserState.userQuery[parserState.pos] === "[") {
|
||||
endChar = "]";
|
||||
name = "[]";
|
||||
friendlyName = "slice";
|
||||
}
|
||||
parserState.pos += 1;
|
||||
const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);
|
||||
const typeFilter = parserState.typeFilter;
|
||||
const isInBinding = parserState.isInBinding;
|
||||
if (typeFilter !== null && typeFilter !== "primitive") {
|
||||
throw [
|
||||
"Invalid search type: primitive ",
|
||||
name,
|
||||
" and ",
|
||||
typeFilter,
|
||||
" both specified",
|
||||
];
|
||||
}
|
||||
const bindingName = parserState.isInBinding;
|
||||
parserState.typeFilter = null;
|
||||
parserState.isInBinding = null;
|
||||
for (const gen of generics) {
|
||||
@ -702,23 +749,26 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
}
|
||||
if (name === "()" && !foundSeparator && generics.length === 1 && typeFilter === null) {
|
||||
elems.push(generics[0]);
|
||||
} else if (name === "()" && generics.length === 1 && generics[0].name === "->") {
|
||||
// `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`
|
||||
// not `primitive:"()"<"->"<output=b, (a,)>>`
|
||||
generics[0].typeFilter = typeFilter;
|
||||
elems.push(generics[0]);
|
||||
} else {
|
||||
if (typeFilter !== null && typeFilter !== "primitive") {
|
||||
throw [
|
||||
"Invalid search type: primitive ",
|
||||
name,
|
||||
" and ",
|
||||
typeFilter,
|
||||
" both specified",
|
||||
];
|
||||
}
|
||||
parserState.totalElems += 1;
|
||||
if (isInGenerics) {
|
||||
parserState.genericsElems += 1;
|
||||
}
|
||||
elems.push({
|
||||
name: name,
|
||||
id: null,
|
||||
fullPath: [name],
|
||||
pathWithoutLast: [],
|
||||
pathLast: name,
|
||||
normalizedPathLast: name,
|
||||
generics,
|
||||
bindings: new Map(),
|
||||
typeFilter: "primitive",
|
||||
bindingName: isInBinding,
|
||||
});
|
||||
elems.push(makePrimitiveElement(name, { bindingName, generics }));
|
||||
}
|
||||
} else {
|
||||
const isStringElem = parserState.userQuery[start] === "\"";
|
||||
@ -738,6 +788,32 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
}
|
||||
parserState.pos += 1;
|
||||
getItemsBefore(query, parserState, generics, ">");
|
||||
} else if (parserState.pos < parserState.length &&
|
||||
parserState.userQuery[parserState.pos] === "("
|
||||
) {
|
||||
if (start >= end) {
|
||||
throw ["Found generics without a path"];
|
||||
}
|
||||
if (parserState.isInBinding) {
|
||||
throw ["Unexpected ", "(", " after ", "="];
|
||||
}
|
||||
parserState.pos += 1;
|
||||
const typeFilter = parserState.typeFilter;
|
||||
parserState.typeFilter = null;
|
||||
getItemsBefore(query, parserState, generics, ")");
|
||||
skipWhitespace(parserState);
|
||||
if (isReturnArrow(parserState)) {
|
||||
parserState.pos += 2;
|
||||
skipWhitespace(parserState);
|
||||
getFilteredNextElem(query, parserState, generics, isInGenerics);
|
||||
generics[generics.length - 1].bindingName = makePrimitiveElement("output");
|
||||
} else {
|
||||
generics.push(makePrimitiveElement(null, {
|
||||
bindingName: makePrimitiveElement("output"),
|
||||
typeFilter: null,
|
||||
}));
|
||||
}
|
||||
parserState.typeFilter = typeFilter;
|
||||
}
|
||||
if (isStringElem) {
|
||||
skipWhitespace(parserState);
|
||||
@ -797,7 +873,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
function getItemsBefore(query, parserState, elems, endChar) {
|
||||
let foundStopChar = true;
|
||||
let foundSeparator = false;
|
||||
let start = parserState.pos;
|
||||
|
||||
// If this is a generic, keep the outer item's type filter around.
|
||||
const oldTypeFilter = parserState.typeFilter;
|
||||
@ -805,6 +880,19 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
const oldIsInBinding = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
|
||||
// ML-style Higher Order Function notation
|
||||
//
|
||||
// a way to search for any closure or fn pointer regardless of
|
||||
// which closure trait is used
|
||||
//
|
||||
// Looks like this:
|
||||
//
|
||||
// `option<t>, (t -> u) -> option<u>`
|
||||
// ^^^^^^
|
||||
//
|
||||
// The Rust-style closure notation is implemented in getNextElem
|
||||
let hofParameters = null;
|
||||
|
||||
let extra = "";
|
||||
if (endChar === ">") {
|
||||
extra = "<";
|
||||
@ -825,6 +913,21 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
throw ["Unexpected ", endChar, " after ", "="];
|
||||
}
|
||||
break;
|
||||
} else if (endChar !== "" && isReturnArrow(parserState)) {
|
||||
// ML-style HOF notation only works when delimited in something,
|
||||
// otherwise a function arrow starts the return type of the top
|
||||
if (parserState.isInBinding) {
|
||||
throw ["Unexpected ", "->", " after ", "="];
|
||||
}
|
||||
hofParameters = [...elems];
|
||||
elems.length = 0;
|
||||
parserState.pos += 2;
|
||||
foundStopChar = true;
|
||||
foundSeparator = false;
|
||||
continue;
|
||||
} else if (c === " ") {
|
||||
parserState.pos += 1;
|
||||
continue;
|
||||
} else if (isSeparatorCharacter(c)) {
|
||||
parserState.pos += 1;
|
||||
foundStopChar = true;
|
||||
@ -832,24 +935,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
continue;
|
||||
} else if (c === ":" && isPathStart(parserState)) {
|
||||
throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
|
||||
} else if (c === ":") {
|
||||
if (parserState.typeFilter !== null) {
|
||||
throw ["Unexpected ", ":"];
|
||||
}
|
||||
if (elems.length === 0) {
|
||||
throw ["Expected type filter before ", ":"];
|
||||
} else if (query.literalSearch) {
|
||||
throw ["Cannot use quotes on type filter"];
|
||||
}
|
||||
// The type filter doesn't count as an element since it's a modifier.
|
||||
const typeFilterElem = elems.pop();
|
||||
checkExtraTypeFilterCharacters(start, parserState);
|
||||
parserState.typeFilter = typeFilterElem.name;
|
||||
parserState.pos += 1;
|
||||
parserState.totalElems -= 1;
|
||||
query.literalSearch = false;
|
||||
foundStopChar = true;
|
||||
continue;
|
||||
} else if (isEndCharacter(c)) {
|
||||
throw ["Unexpected ", c, " after ", extra];
|
||||
}
|
||||
@ -884,8 +969,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
];
|
||||
}
|
||||
const posBefore = parserState.pos;
|
||||
start = parserState.pos;
|
||||
getNextElem(query, parserState, elems, endChar !== "");
|
||||
getFilteredNextElem(query, parserState, elems, endChar !== "");
|
||||
if (endChar !== "" && parserState.pos >= parserState.length) {
|
||||
throw ["Unclosed ", extra];
|
||||
}
|
||||
@ -904,6 +988,27 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
// in any case.
|
||||
parserState.pos += 1;
|
||||
|
||||
if (hofParameters) {
|
||||
// Commas in a HOF don't cause wrapping parens to become a tuple.
|
||||
// If you want a one-tuple with a HOF in it, write `((a -> b),)`.
|
||||
foundSeparator = false;
|
||||
// HOFs can't have directly nested bindings.
|
||||
if ([...elems, ...hofParameters].some(x => x.bindingName) || parserState.isInBinding) {
|
||||
throw ["Unexpected ", "=", " within ", "->"];
|
||||
}
|
||||
// HOFs are represented the same way closures are.
|
||||
// The arguments are wrapped in a tuple, and the output
|
||||
// is a binding, even though the compiler doesn't technically
|
||||
// represent fn pointers that way.
|
||||
const hofElem = makePrimitiveElement("->", {
|
||||
generics: hofParameters,
|
||||
bindings: new Map([["output", [...elems]]]),
|
||||
typeFilter: null,
|
||||
});
|
||||
elems.length = 0;
|
||||
elems[0] = hofElem;
|
||||
}
|
||||
|
||||
parserState.typeFilter = oldTypeFilter;
|
||||
parserState.isInBinding = oldIsInBinding;
|
||||
|
||||
@ -941,7 +1046,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
*/
|
||||
function parseInput(query, parserState) {
|
||||
let foundStopChar = true;
|
||||
let start = parserState.pos;
|
||||
|
||||
while (parserState.pos < parserState.length) {
|
||||
const c = parserState.userQuery[parserState.pos];
|
||||
@ -959,29 +1063,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]];
|
||||
}
|
||||
throw ["Unexpected ", c];
|
||||
} else if (c === ":" && !isPathStart(parserState)) {
|
||||
if (parserState.typeFilter !== null) {
|
||||
throw [
|
||||
"Unexpected ",
|
||||
":",
|
||||
" (expected path after type filter ",
|
||||
parserState.typeFilter + ":",
|
||||
")",
|
||||
];
|
||||
} else if (query.elems.length === 0) {
|
||||
throw ["Expected type filter before ", ":"];
|
||||
} else if (query.literalSearch) {
|
||||
throw ["Cannot use quotes on type filter"];
|
||||
}
|
||||
// The type filter doesn't count as an element since it's a modifier.
|
||||
const typeFilterElem = query.elems.pop();
|
||||
checkExtraTypeFilterCharacters(start, parserState);
|
||||
parserState.typeFilter = typeFilterElem.name;
|
||||
parserState.pos += 1;
|
||||
parserState.totalElems -= 1;
|
||||
query.literalSearch = false;
|
||||
foundStopChar = true;
|
||||
continue;
|
||||
} else if (c === " ") {
|
||||
skipWhitespace(parserState);
|
||||
continue;
|
||||
@ -1017,8 +1098,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
];
|
||||
}
|
||||
const before = query.elems.length;
|
||||
start = parserState.pos;
|
||||
getNextElem(query, parserState, query.elems, false);
|
||||
getFilteredNextElem(query, parserState, query.elems, false);
|
||||
if (query.elems.length === before) {
|
||||
// Nothing was added, weird... Let's increase the position to not remain stuck.
|
||||
parserState.pos += 1;
|
||||
@ -1258,11 +1338,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
* @returns {[ResultObject]}
|
||||
*/
|
||||
function sortResults(results, isType, preferredCrate) {
|
||||
// if there are no results then return to default and fail
|
||||
if (results.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const userQuery = parsedQuery.userQuery;
|
||||
const result_list = [];
|
||||
for (const result of results.values()) {
|
||||
@ -1635,6 +1710,12 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
) {
|
||||
// () matches primitive:tuple or primitive:unit
|
||||
// if it matches, then we're fine, and this is an appropriate match candidate
|
||||
} else if (queryElem.id === typeNameIdOfHof &&
|
||||
(fnType.id === typeNameIdOfFn || fnType.id === typeNameIdOfFnMut ||
|
||||
fnType.id === typeNameIdOfFnOnce)
|
||||
) {
|
||||
// -> matches fn, fnonce, and fnmut
|
||||
// if it matches, then we're fine, and this is an appropriate match candidate
|
||||
} else if (fnType.id !== queryElem.id || queryElem.id === null) {
|
||||
return false;
|
||||
}
|
||||
@ -1829,6 +1910,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
|
||||
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
|
||||
// special case
|
||||
elem.id !== typeNameIdOfArrayOrSlice && elem.id !== typeNameIdOfTupleOrUnit
|
||||
&& elem.id !== typeNameIdOfHof
|
||||
) {
|
||||
return row.id === elem.id || checkIfInList(
|
||||
row.generics,
|
||||
@ -2991,7 +3073,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
*/
|
||||
function buildFunctionTypeFingerprint(type, output, fps) {
|
||||
let input = type.id;
|
||||
// All forms of `[]`/`()` get collapsed down to one thing in the bloom filter.
|
||||
// All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter.
|
||||
// Differentiating between arrays and slices, if the user asks for it, is
|
||||
// still done in the matching algorithm.
|
||||
if (input === typeNameIdOfArray || input === typeNameIdOfSlice) {
|
||||
@ -3000,6 +3082,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
if (input === typeNameIdOfTuple || input === typeNameIdOfUnit) {
|
||||
input = typeNameIdOfTupleOrUnit;
|
||||
}
|
||||
if (input === typeNameIdOfFn || input === typeNameIdOfFnMut ||
|
||||
input === typeNameIdOfFnOnce) {
|
||||
input = typeNameIdOfHof;
|
||||
}
|
||||
// http://burtleburtle.net/bob/hash/integer.html
|
||||
// ~~ is toInt32. It's used before adding, so
|
||||
// the number stays in safe integer range.
|
||||
@ -3090,20 +3176,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
*/
|
||||
function buildIndex(rawSearchIndex) {
|
||||
searchIndex = [];
|
||||
typeNameIdMap = new Map();
|
||||
const charA = "A".charCodeAt(0);
|
||||
let currentIndex = 0;
|
||||
let id = 0;
|
||||
|
||||
// Initialize type map indexes for primitive list types
|
||||
// that can be searched using `[]` syntax.
|
||||
typeNameIdOfArray = buildTypeMapIndex("array");
|
||||
typeNameIdOfSlice = buildTypeMapIndex("slice");
|
||||
typeNameIdOfTuple = buildTypeMapIndex("tuple");
|
||||
typeNameIdOfUnit = buildTypeMapIndex("unit");
|
||||
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
|
||||
typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
|
||||
|
||||
// Function type fingerprints are 128-bit bloom filters that are used to
|
||||
// estimate the distance between function and query.
|
||||
// This loop counts the number of items to allocate a fingerprint for.
|
||||
|
@ -114,7 +114,7 @@ const PARSED = [
|
||||
original: "(p -> p",
|
||||
returned: [],
|
||||
userQuery: "(p -> p",
|
||||
error: "Unexpected `-` after `(`",
|
||||
error: "Unclosed `(`",
|
||||
},
|
||||
{
|
||||
query: "::a::b",
|
||||
@ -195,7 +195,7 @@ const PARSED = [
|
||||
original: "a (b:",
|
||||
returned: [],
|
||||
userQuery: "a (b:",
|
||||
error: "Expected `,`, `:` or `->`, found `(`",
|
||||
error: "Unclosed `(`",
|
||||
},
|
||||
{
|
||||
query: "_:",
|
||||
@ -330,7 +330,7 @@ const PARSED = [
|
||||
original: 'a<->',
|
||||
returned: [],
|
||||
userQuery: 'a<->',
|
||||
error: 'Unexpected `-` after `<`',
|
||||
error: 'Unclosed `<`',
|
||||
},
|
||||
{
|
||||
query: "a<a>:",
|
||||
@ -357,7 +357,16 @@ const PARSED = [
|
||||
original: "a,:",
|
||||
returned: [],
|
||||
userQuery: "a,:",
|
||||
error: 'Unexpected `,` in type filter (before `:`)',
|
||||
error: 'Expected type filter before `:`',
|
||||
},
|
||||
{
|
||||
query: "a!:",
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: "a!:",
|
||||
returned: [],
|
||||
userQuery: "a!:",
|
||||
error: 'Unexpected `!` in type filter (before `:`)',
|
||||
},
|
||||
{
|
||||
query: " a<> :",
|
||||
@ -366,7 +375,7 @@ const PARSED = [
|
||||
original: "a<> :",
|
||||
returned: [],
|
||||
userQuery: "a<> :",
|
||||
error: 'Unexpected `<` in type filter (before `:`)',
|
||||
error: 'Expected `,`, `:` or `->` after `>`, found `:`',
|
||||
},
|
||||
{
|
||||
query: "mod : :",
|
||||
|
712
tests/rustdoc-js-std/parser-hof.js
Normal file
712
tests/rustdoc-js-std/parser-hof.js
Normal file
@ -0,0 +1,712 @@
|
||||
const PARSED = [
|
||||
// ML-style HOF
|
||||
{
|
||||
query: "(-> F<P>)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "f",
|
||||
fullPath: ["f"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "f",
|
||||
generics: [
|
||||
{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
},
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(-> F<P>)",
|
||||
returned: [],
|
||||
userQuery: "(-> f<p>)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(-> P)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(-> P)",
|
||||
returned: [],
|
||||
userQuery: "(-> p)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(->,a)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(->,a)",
|
||||
returned: [],
|
||||
userQuery: "(->,a)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(F<P> ->)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [{
|
||||
name: "f",
|
||||
fullPath: ["f"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "f",
|
||||
generics: [
|
||||
{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
},
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(F<P> ->)",
|
||||
returned: [],
|
||||
userQuery: "(f<p> ->)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(P ->)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(P ->)",
|
||||
returned: [],
|
||||
userQuery: "(p ->)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(,a->)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(,a->)",
|
||||
returned: [],
|
||||
userQuery: "(,a->)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(aaaaa->a)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [{
|
||||
name: "aaaaa",
|
||||
fullPath: ["aaaaa"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "aaaaa",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(aaaaa->a)",
|
||||
returned: [],
|
||||
userQuery: "(aaaaa->a)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(aaaaa, b -> a)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [
|
||||
{
|
||||
name: "aaaaa",
|
||||
fullPath: ["aaaaa"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "aaaaa",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
fullPath: ["b"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "b",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(aaaaa, b -> a)",
|
||||
returned: [],
|
||||
userQuery: "(aaaaa, b -> a)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "primitive:(aaaaa, b -> a)",
|
||||
elems: [{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [
|
||||
{
|
||||
name: "aaaaa",
|
||||
fullPath: ["aaaaa"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "aaaaa",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
fullPath: ["b"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "b",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: 1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "primitive:(aaaaa, b -> a)",
|
||||
returned: [],
|
||||
userQuery: "primitive:(aaaaa, b -> a)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "x, trait:(aaaaa, b -> a)",
|
||||
elems: [
|
||||
{
|
||||
name: "x",
|
||||
fullPath: ["x"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "x",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [
|
||||
{
|
||||
name: "aaaaa",
|
||||
fullPath: ["aaaaa"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "aaaaa",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
fullPath: ["b"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "b",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: 10,
|
||||
}
|
||||
],
|
||||
foundElems: 2,
|
||||
original: "x, trait:(aaaaa, b -> a)",
|
||||
returned: [],
|
||||
userQuery: "x, trait:(aaaaa, b -> a)",
|
||||
error: null,
|
||||
},
|
||||
// Rust-style HOF
|
||||
{
|
||||
query: "Fn () -> F<P>",
|
||||
elems: [{
|
||||
name: "fn",
|
||||
fullPath: ["fn"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "fn",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "f",
|
||||
fullPath: ["f"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "f",
|
||||
generics: [
|
||||
{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
},
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "Fn () -> F<P>",
|
||||
returned: [],
|
||||
userQuery: "fn () -> f<p>",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "FnMut() -> P",
|
||||
elems: [{
|
||||
name: "fnmut",
|
||||
fullPath: ["fnmut"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "fnmut",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "FnMut() -> P",
|
||||
returned: [],
|
||||
userQuery: "fnmut() -> p",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "(FnMut() -> P)",
|
||||
elems: [{
|
||||
name: "fnmut",
|
||||
fullPath: ["fnmut"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "fnmut",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "(FnMut() -> P)",
|
||||
returned: [],
|
||||
userQuery: "(fnmut() -> p)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "Fn(F<P>)",
|
||||
elems: [{
|
||||
name: "fn",
|
||||
fullPath: ["fn"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "fn",
|
||||
generics: [{
|
||||
name: "f",
|
||||
fullPath: ["f"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "f",
|
||||
generics: [
|
||||
{
|
||||
name: "p",
|
||||
fullPath: ["p"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "p",
|
||||
generics: [],
|
||||
},
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "Fn(F<P>)",
|
||||
returned: [],
|
||||
userQuery: "fn(f<p>)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "primitive:fnonce(aaaaa, b) -> a",
|
||||
elems: [{
|
||||
name: "fnonce",
|
||||
fullPath: ["fnonce"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "fnonce",
|
||||
generics: [
|
||||
{
|
||||
name: "aaaaa",
|
||||
fullPath: ["aaaaa"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "aaaaa",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
fullPath: ["b"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "b",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: 1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "primitive:fnonce(aaaaa, b) -> a",
|
||||
returned: [],
|
||||
userQuery: "primitive:fnonce(aaaaa, b) -> a",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
|
||||
elems: [{
|
||||
name: "fnonce",
|
||||
fullPath: ["fnonce"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "fnonce",
|
||||
generics: [
|
||||
{
|
||||
name: "aaaaa",
|
||||
fullPath: ["aaaaa"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "aaaaa",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
fullPath: ["b"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "b",
|
||||
generics: [],
|
||||
typeFilter: 0,
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: 10,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: 1,
|
||||
}],
|
||||
foundElems: 1,
|
||||
original: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
|
||||
returned: [],
|
||||
userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: "x, trait:fn(aaaaa, b -> a)",
|
||||
elems: [
|
||||
{
|
||||
name: "x",
|
||||
fullPath: ["x"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "x",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "fn",
|
||||
fullPath: ["fn"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "fn",
|
||||
generics: [
|
||||
{
|
||||
name: "->",
|
||||
fullPath: ["->"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "->",
|
||||
generics: [
|
||||
{
|
||||
name: "aaaaa",
|
||||
fullPath: ["aaaaa"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "aaaaa",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
fullPath: ["b"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "b",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[],
|
||||
]
|
||||
],
|
||||
typeFilter: 10,
|
||||
}
|
||||
],
|
||||
foundElems: 2,
|
||||
original: "x, trait:fn(aaaaa, b -> a)",
|
||||
returned: [],
|
||||
userQuery: "x, trait:fn(aaaaa, b -> a)",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'a,b(c)',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
fullPath: ["b"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "b",
|
||||
generics: [{
|
||||
name: "c",
|
||||
fullPath: ["c"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "c",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}],
|
||||
bindings: [
|
||||
[
|
||||
"output",
|
||||
[],
|
||||
]
|
||||
],
|
||||
typeFilter: -1,
|
||||
}
|
||||
],
|
||||
foundElems: 2,
|
||||
original: "a,b(c)",
|
||||
returned: [],
|
||||
userQuery: "a,b(c)",
|
||||
error: null,
|
||||
},
|
||||
];
|
@ -37,15 +37,6 @@ const PARSED = [
|
||||
userQuery: "a b",
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'a,b(c)',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: "a,b(c)",
|
||||
returned: [],
|
||||
userQuery: "a,b(c)",
|
||||
error: "Expected `,`, `:` or `->`, found `(`",
|
||||
},
|
||||
{
|
||||
query: 'aaa,a',
|
||||
elems: [
|
||||
|
176
tests/rustdoc-js/hof.js
Normal file
176
tests/rustdoc-js/hof.js
Normal file
@ -0,0 +1,176 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
// not a HOF query
|
||||
{
|
||||
'query': 'u32 -> !',
|
||||
'others': [],
|
||||
},
|
||||
|
||||
// ML-style higher-order function notation
|
||||
{
|
||||
'query': 'bool, (u32 -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_ptr"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'u8, (u32 -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_once"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'i8, (u32 -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_mut"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'char, (u32 -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(first<u32> -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_ptr"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(second<u32> -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_once"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(third<u32> -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_mut"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(u32 -> !) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_"},
|
||||
{"path": "hof", "name": "fn_ptr"},
|
||||
{"path": "hof", "name": "fn_mut"},
|
||||
{"path": "hof", "name": "fn_once"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(str, str -> i8) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "multiple"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(str ->) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "multiple"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(-> i8) -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "multiple"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': '(str -> str) -> ()',
|
||||
// params and return are not the same
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': '(i8 ->) -> ()',
|
||||
// params and return are not the same
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': '(-> str) -> ()',
|
||||
// params and return are not the same
|
||||
'others': [],
|
||||
},
|
||||
|
||||
// Rust-style higher-order function notation
|
||||
{
|
||||
'query': 'bool, fn(u32) -> ! -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_ptr"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'u8, fnonce(u32) -> ! -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_once"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'u8, fn(u32) -> ! -> ()',
|
||||
// fnonce != fn
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'i8, fnmut(u32) -> ! -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_mut"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'i8, fn(u32) -> ! -> ()',
|
||||
// fnmut != fn
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'char, fn(u32) -> ! -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'char, fnmut(u32) -> ! -> ()',
|
||||
// fn != fnmut
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'fn(first<u32>) -> ! -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_ptr"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'fnonce(second<u32>) -> ! -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_once"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'fnmut(third<u32>) -> ! -> ()',
|
||||
'others': [
|
||||
{"path": "hof", "name": "fn_mut"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'fn(u32) -> ! -> ()',
|
||||
'others': [
|
||||
// fn matches primitive:fn and trait:Fn
|
||||
{"path": "hof", "name": "fn_"},
|
||||
{"path": "hof", "name": "fn_ptr"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'trait:fn(u32) -> ! -> ()',
|
||||
'others': [
|
||||
// fn matches primitive:fn and trait:Fn
|
||||
{"path": "hof", "name": "fn_"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'primitive:fn(u32) -> ! -> ()',
|
||||
'others': [
|
||||
// fn matches primitive:fn and trait:Fn
|
||||
{"path": "hof", "name": "fn_ptr"},
|
||||
],
|
||||
},
|
||||
];
|
12
tests/rustdoc-js/hof.rs
Normal file
12
tests/rustdoc-js/hof.rs
Normal file
@ -0,0 +1,12 @@
|
||||
#![feature(never_type)]
|
||||
|
||||
pub struct First<T>(T);
|
||||
pub struct Second<T>(T);
|
||||
pub struct Third<T>(T);
|
||||
|
||||
pub fn fn_ptr(_: fn (First<u32>) -> !, _: bool) {}
|
||||
pub fn fn_once(_: impl FnOnce (Second<u32>) -> !, _: u8) {}
|
||||
pub fn fn_mut(_: impl FnMut (Third<u32>) -> !, _: i8) {}
|
||||
pub fn fn_(_: impl Fn (u32) -> !, _: char) {}
|
||||
|
||||
pub fn multiple(_: impl Fn(&'static str, &'static str) -> i8) {}
|
Loading…
x
Reference in New Issue
Block a user