Rollup merge of #112725 - notriddle:notriddle/advanced-search, r=GuillaumeGomez

rustdoc-search: add support for type parameters

r? `@GuillaumeGomez`

## Preview

* https://notriddle.com/rustdoc-html-demo-4/advanced-search/rustdoc/read-documentation/search.html
* https://notriddle.com/rustdoc-html-demo-4/advanced-search/std/index.html?search=option%3Coption%3CT%3E%3E%20-%3E%20option%3CT%3E
* https://notriddle.com/rustdoc-html-demo-4/advanced-search/std/index.html?search=option%3CT%3E,%20E%20-%3E%20result%3CT,%20E%3E
* https://notriddle.com/rustdoc-html-demo-4/advanced-search/std/index.html?search=-%3E%20option%3CT%3E

## Description

When writing a type-driven search query in rustdoc, specifically one with more than one query element, non-existent types become generic parameters instead of auto-correcting (which is currently only done for single-element queries) or giving no result. You can also force a generic type parameter by writing `generic:T` (and can force it to not use a generic type parameter with something like `struct:T` or whatever, though if this happens it means the thing you're looking for doesn't exist and will give you no results).

There is no syntax provided for specifying type constraints for generic type parameters.

When you have a generic type parameter in a search query, it will only match up with generic type parameters in the actual function, not concrete types that match, not concrete types that implement a trait. It also strictly matches based on when they're the same or different, so `option<T>, option<U> -> option<U>` matches `Option::and`, but not `Option::or`. Similarly, `option<T>, option<T> -> option<T>` matches `Option::or`, but not `Option::and`.

## Motivation

This feature is motivated by the many "combinitor"-type functions found in generic libraries, such as Option, Future, Iterator, and Entry. These highly-generic functions have names that are almost completely arbitrary, and a type signature that tells you what it actually does.

This PR is a major step towards[^closure] being able to easily search for generic functions by their type signature instead of by name. Some examples of combinators that can be found using this PR (try them out in the preview):

* `option<option<T>> -> option<T>` returns Option::flatten
* `option<T> -> result<T>` returns Option::ok_or
* `option<result<T>> -> result<option<T>>` returns Option::transpose
* `entry<K, V>, FnOnce -> V` returns `Entry::or_insert_with` (and `or_insert_with_key`, since there's no way to specify the generics on FnOnce)

[^closure]:

    For this feature to be as useful as it ought to be, you should be able to search for *trait-associated types* and *closures*. This PR does not implement either of these: they are **Future possibilities**.

    Trait-associated types would allow queries like `option<T> -> iterator<item=T>` to return `Option::iter`. We should also allow `option<T> -> iterator<T>` to match the associated type version.

    Closures would make a good way to query for things like `Option::map`. Closure support needs associated types to be represented in the search index, since `FnOnce() -> i32` desugars to `FnOnce<Output=i32, ()>`, so associated trait types should be implemented first. Also, we'd want to expose an easy way to query closures without specifying which of the three traits you want.
This commit is contained in:
Guillaume Gomez 2023-09-19 11:35:49 +02:00 committed by GitHub
commit 3f68468bc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1208 additions and 471 deletions

View file

@ -4,6 +4,7 @@
- [Command-line arguments](command-line-arguments.md)
- [How to read rustdoc output](how-to-read-rustdoc.md)
- [In-doc settings](read-documentation/in-doc-settings.md)
- [Search](read-documentation/search.md)
- [How to write documentation](how-to-write-documentation.md)
- [What to include (and exclude)](write-documentation/what-to-include.md)
- [The `#[doc]` attribute](write-documentation/the-doc-attribute.md)

View file

@ -75,56 +75,11 @@ or the current item whose documentation is being displayed.
## The Theme Picker and Search Interface
When viewing `rustdoc`'s output in a browser with JavaScript enabled,
a dynamic interface appears at the top of the page composed of the search
interface, help screen, and options.
a dynamic interface appears at the top of the page composed of the [search]
interface, help screen, and [options].
### The Search Interface
Typing in the search bar instantly searches the available documentation for
the string entered with a fuzzy matching algorithm that is tolerant of minor
typos.
By default, the search results given are "In Names",
meaning that the fuzzy match is made against the names of items.
Matching names are shown on the left, and the first few words of their
descriptions are given on the right.
By clicking an item, you will navigate to its particular documentation.
There are two other sets of results, shown as tabs in the search results pane.
"In Parameters" shows matches for the string in the types of parameters to
functions, and "In Return Types" shows matches in the return types of functions.
Both are very useful when looking for a function whose name you can't quite
bring to mind when you know the type you have or want.
Names in the search interface can be prefixed with an item type followed by a
colon (such as `mod:`) to restrict the results to just that kind of item. Also,
searching for `println!` will search for a macro named `println`, just like
searching for `macro:println` does.
Function signature searches can query generics, wrapped in angle brackets, and
traits are normalized like types in the search engine. For example, a function
with the signature `fn my_function<I: Iterator<Item=u32>>(input: I) -> usize`
can be matched with the following queries:
* `Iterator<u32> -> usize`
* `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>>`.
Function signature searches also support arrays and slices. The explicit name
`primitive:slice<u8>` and `primitive:array<u8>` can be used to match a slice
or array of bytes, while square brackets `[u8]` will match either one. Empty
square brackets, `[]`, will match any slice regardless of what it contains.
[options]: read-documentation/in-doc-settings.html
[search]: read-documentation/search.md
Paths are supported as well, you can look for `Vec::new` or `Option::Some` or
even `module::module_child::another_child::struct::field`. Whitespace characters

View file

@ -0,0 +1,237 @@
# Rustdoc search
Typing in the search bar instantly searches the available documentation,
matching either the name and path of an item, or a function's approximate
type signature.
## Search By Name
To search by the name of an item (items include modules, types, traits,
functions, and macros), write its name or path. As a special case, the parts
of a path that normally get divided by `::` double colons can instead be
separated by spaces. For example:
* [`vec new`] and [`vec::new`] both show the function `std::vec::Vec::new`
as a result.
* [`vec`], [`vec vec`], [`std::vec`], and [`std::vec::Vec`] all include the struct
`std::vec::Vec` itself in the results (and all but the last one also
include the module in the results).
[`vec new`]: ../../std/vec/struct.Vec.html?search=vec%20new&filter-crate=std
[`vec::new`]: ../../std/vec/struct.Vec.html?search=vec::new&filter-crate=std
[`vec`]: ../../std/vec/struct.Vec.html?search=vec&filter-crate=std
[`vec vec`]: ../../std/vec/struct.Vec.html?search=vec%20vec&filter-crate=std
[`std::vec`]: ../../std/vec/struct.Vec.html?search=std::vec&filter-crate=std
[`std::vec::Vec`]: ../../std/vec/struct.Vec.html?search=std::vec::Vec&filter-crate=std
[`std::vec::Vec`]: ../../std/vec/struct.Vec.html?search=std::vec::Vec&filter-crate=std
As a quick way to trim down the list of results, there's a drop-down selector
below the search input, labeled "Results in \[std\]". Clicking it can change
which crate is being searched.
Rustdoc uses a fuzzy matching function that can tolerate typos for this,
though it's based on the length of the name that's typed in, so a good example
of how this works would be [`HahsMap`]. To avoid this, wrap the item in quotes,
searching for `"HahsMap"` (in this example, no results will be returned).
[`HahsMap`]: ../../std/collections/struct.HashMap.html?search=HahsMap&filter-crate=std
### Tabs in the Search By Name interface
In fact, using [`HahsMap`] again as the example, it tells you that you're
using "In Names" by default, but also lists two other tabs below the crate
drop-down: "In Parameters" and "In Return Types".
These two tabs are lists of functions, defined on the closest matching type
to the search (for `HahsMap`, it loudly auto-corrects to `hashmap`). This
auto-correct only kicks in if nothing is found that matches the literal.
These tabs are not just methods. For example, searching the alloc crate for
[`Layout`] also lists functions that accept layouts even though they're
methods on the allocator or free functions.
[`Layout`]: ../../alloc/index.html?search=Layout&filter-crate=alloc
## Searching By Type Signature for functions
If you know more specifically what the function you want to look at does,
Rustdoc can search by more than one type at once in the parameters and return
value. Multiple parameters are separated by `,` commas, and the return value
is written with after a `->` arrow.
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 -> default`][] | `Option::unwrap_or_default` |
| [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
| [`any -> !`][] | `panic::panic_any` |
| [`vec::intoiter<T> -> [T]`][iterasslice] | `IntoIter::as_slice` and `IntoIter::next_chunk` |
[`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
[`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
[iterasslice]: ../../std/vec/struct.Vec.html?search=vec%3A%3Aintoiter<T>%20->%20[T]&filter-crate=std
### How type-based search works
In a complex type-based search, Rustdoc always treats every item's name as literal.
If a name is used and nothing in the docs matches the individual item, such as
a typo-ed [`uize -> vec`][] search, the item `uize` is treated as a generic
type parameter (resulting in `vec::from` and other generic vec constructors).
[`uize -> vec`]: ../../std/vec/struct.Vec.html?search=uize%20-%3E%20vec&filter-crate=std
After deciding which items are type parameters and which are actual types, it
then searches by matching up the function parameters (written before the `->`)
and the return types (written after the `->`). Type matching is order-agnostic,
and allows items to be left out of the query, but items that are present in the
query must be present in the function for it to match.
Function signature searches can query generics, wrapped in angle brackets, and
traits will be normalized like types in the search engine if no type parameters
match them. For example, a function with the signature
`fn my_function<I: Iterator<Item=u32>>(input: I) -> usize`
can be matched with the following queries:
* `Iterator<u32> -> 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>>`.
Function signature searches also support arrays and slices. The explicit name
`primitive:slice<u8>` and `primitive:array<u8>` can be used to match a slice
or array of bytes, while square brackets `[u8]` will match either one. Empty
square brackets, `[]`, will match any slice or array regardless of what
it contains, while a slice with a type parameter, like `[T]`, will only match
functions that actually operate on generic slices.
### Limitations and quirks of type-based search
Type-based search is still a buggy, experimental, work-in-progress feature.
Most of these limitations should be addressed in future version of Rustdoc.
* There's no way to write trait constraints on generic parameters.
You can name traits directly, and if there's a type parameter
with that bound, it'll match, but `option<T> -> T where T: Default`
cannot be precisely searched for (use `option<Default> -> Default`).
* Type parameters match type parameters, such that `Option<A>` matches
`Option<T>`, but never match concrete types in function signatures.
A trait named as if it were a type, such as `Option<Read>`, will match
a type parameter constrained by that trait, such as
`Option<T> where T: Read`, as well as matching `dyn Trait` and
`impl Trait`.
* `impl Trait` in argument position is treated exactly like a type
parameter, but in return position it will not match type parameters.
* Any type named in a complex type-based search will be assumed to be a
type parameter if nothing matching the name exactly is found. If you
want to force a type parameter, write `generic:T` and it will be used
as a type parameter even if a matching name is found. If you know
that you don't want a type parameter, you can force it to match
something else by giving it a different prefix like `struct:T`.
* It's impossible to search for references, pointers, or tuples. The
wrapped types can be searched for, so a function that takes `&File` can
be found with `File`, but you'll get a parse error when typing an `&`
into the search field. Similarly, `Option<(T, U)>` can be matched with
`Option<T, U>`, but `(` will give a parse error.
* 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
Names in the search interface can be prefixed with an item type followed by a
colon (such as `mod:`) to restrict the results to just that kind of item. Also,
searching for `println!` will search for a macro named `println`, just like
searching for `macro:println` does. The complete list of available filters is
given under the <kbd>?</kbd> Help area, and in the detailed syntax below.
Item filters can be used in both name-based and type signature-based searches.
## Search query syntax
```text
ident = *(ALPHA / DIGIT / "_")
path = ident *(DOUBLE-COLON ident) [!]
slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
arg = [type-filter *WS COLON *WS] (path [generics] / slice / [!])
type-sep = COMMA/WS *(COMMA/WS)
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
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 ]
type-search = [ nonempty-arg-list ] [ return-args ]
query = *WS (exact-search / type-search) *WS
type-filter = (
"mod" /
"externcrate" /
"import" /
"struct" /
"enum" /
"fn" /
"type" /
"static" /
"trait" /
"impl" /
"tymethod" /
"method" /
"structfield" /
"variant" /
"macro" /
"primitive" /
"associatedtype" /
"constant" /
"associatedconstant" /
"union" /
"foreigntype" /
"keyword" /
"existential" /
"attr" /
"derive" /
"traitalias" /
"generic")
OPEN-ANGLE-BRACKET = "<"
CLOSE-ANGLE-BRACKET = ">"
OPEN-SQUARE-BRACKET = "["
CLOSE-SQUARE-BRACKET = "]"
COLON = ":"
DOUBLE-COLON = "::"
QUOTE = %x22
COMMA = ","
RETURN-ARROW = "->"
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39
WS = %x09 / " "
```

View file

@ -1639,10 +1639,6 @@ impl Type {
matches!(self, Type::Generic(_))
}
pub(crate) fn is_impl_trait(&self) -> bool {
matches!(self, Type::ImplTrait(_))
}
pub(crate) fn is_unit(&self) -> bool {
matches!(self, Type::Tuple(v) if v.is_empty())
}

View file

@ -101,7 +101,7 @@ pub(crate) struct IndexItem {
pub(crate) path: String,
pub(crate) desc: String,
pub(crate) parent: Option<DefId>,
pub(crate) parent_idx: Option<usize>,
pub(crate) parent_idx: Option<isize>,
pub(crate) search_type: Option<IndexItemFunctionType>,
pub(crate) aliases: Box<[Symbol]>,
pub(crate) deprecation: Option<Deprecation>,
@ -122,7 +122,10 @@ impl Serialize for RenderType {
let id = match &self.id {
// 0 is a sentinel, everything else is one-indexed
None => 0,
Some(RenderTypeId::Index(idx)) => idx + 1,
// concrete type
Some(RenderTypeId::Index(idx)) if *idx >= 0 => idx + 1,
// generic type parameter
Some(RenderTypeId::Index(idx)) => *idx,
_ => panic!("must convert render types to indexes before serializing"),
};
if let Some(generics) = &self.generics {
@ -140,7 +143,7 @@ impl Serialize for RenderType {
pub(crate) enum RenderTypeId {
DefId(DefId),
Primitive(clean::PrimitiveType),
Index(usize),
Index(isize),
}
/// Full type of functions/methods in the search index.
@ -148,6 +151,7 @@ pub(crate) enum RenderTypeId {
pub(crate) struct IndexItemFunctionType {
inputs: Vec<RenderType>,
output: Vec<RenderType>,
where_clause: Vec<Vec<RenderType>>,
}
impl Serialize for IndexItemFunctionType {
@ -170,10 +174,17 @@ impl Serialize for IndexItemFunctionType {
_ => seq.serialize_element(&self.inputs)?,
}
match &self.output[..] {
[] => {}
[] if self.where_clause.is_empty() => {}
[one] if one.generics.is_none() => seq.serialize_element(one)?,
_ => seq.serialize_element(&self.output)?,
}
for constraint in &self.where_clause {
if let [one] = &constraint[..] && one.generics.is_none() {
seq.serialize_element(one)?;
} else {
seq.serialize_element(constraint)?;
}
}
seq.end()
}
}

View file

@ -68,16 +68,16 @@ pub(crate) fn build_index<'tcx>(
// Reduce `DefId` in paths into smaller sequential numbers,
// and prune the paths that do not appear in the index.
let mut lastpath = "";
let mut lastpathid = 0usize;
let mut lastpathid = 0isize;
// First, on function signatures
let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new());
for item in search_index.iter_mut() {
fn insert_into_map<F: std::hash::Hash + Eq>(
ty: &mut RenderType,
map: &mut FxHashMap<F, usize>,
map: &mut FxHashMap<F, isize>,
itemid: F,
lastpathid: &mut usize,
lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
item_type: ItemType,
path: &[Symbol],
@ -97,9 +97,9 @@ pub(crate) fn build_index<'tcx>(
fn convert_render_type(
ty: &mut RenderType,
cache: &mut Cache,
itemid_to_pathid: &mut FxHashMap<ItemId, usize>,
primitives: &mut FxHashMap<Symbol, usize>,
lastpathid: &mut usize,
itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
primitives: &mut FxHashMap<Symbol, isize>,
lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
) {
if let Some(generics) = &mut ty.generics {
@ -173,6 +173,18 @@ pub(crate) fn build_index<'tcx>(
&mut crate_paths,
);
}
for constraint in &mut search_type.where_clause {
for trait_ in &mut constraint[..] {
convert_render_type(
trait_,
cache,
&mut itemid_to_pathid,
&mut primitives,
&mut lastpathid,
&mut crate_paths,
);
}
}
}
}
@ -402,7 +414,7 @@ pub(crate) fn get_function_type_for_search<'tcx>(
impl_generics: Option<&(clean::Type, clean::Generics)>,
cache: &Cache,
) -> Option<IndexItemFunctionType> {
let (mut inputs, mut output) = match *item.kind {
let (mut inputs, mut output, where_clause) = match *item.kind {
clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache),
clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
@ -412,7 +424,7 @@ pub(crate) fn get_function_type_for_search<'tcx>(
inputs.retain(|a| a.id.is_some() || a.generics.is_some());
output.retain(|a| a.id.is_some() || a.generics.is_some());
Some(IndexItemFunctionType { inputs, output })
Some(IndexItemFunctionType { inputs, output, where_clause })
}
fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType {
@ -432,96 +444,48 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
get_index_type_id(type_)
}
// The type parameters are converted to generics in `add_generics_and_bounds_as_types`
// 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::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)),
// Not supported yet
clean::BareFunction(_)
| clean::Generic(_)
| clean::ImplTrait(_)
| clean::Tuple(_)
| clean::QPath { .. }
| clean::Infer => None,
}
}
/// The point of this function is to replace bounds with types.
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
enum SimplifiedParam {
// other kinds of type parameters are identified by their name
Symbol(Symbol),
// every argument-position impl trait is its own type parameter
Anonymous(isize),
}
/// The point of this function is to lower generics and types into the simplified form that the
/// frontend search engine can use.
///
/// i.e. `[T, U]` when you have the following bounds: `T: Display, U: Option<T>` will return
/// `[Display, Option]`. If a type parameter has no trait bound, it is discarded.
/// For example, `[T, U, i32]]` where you have the bounds: `T: Display, U: Option<T>` will return
/// `[-1, -2, i32] where -1: Display, -2: Option<-1>`. If a type parameter has no traid bound, it
/// will still get a number. If a constraint is present but not used in the actual types, it will
/// not be added to the map.
///
/// Important note: It goes through generics recursively. So if you have
/// `T: Option<Result<(), ()>>`, it'll go into `Option` and then into `Result`.
#[instrument(level = "trace", skip(tcx, res, cache))]
fn add_generics_and_bounds_as_types<'tcx, 'a>(
/// This function also works recursively.
#[instrument(level = "trace", skip(tcx, res, rgen, cache))]
fn simplify_fn_type<'tcx, 'a>(
self_: Option<&'a Type>,
generics: &Generics,
arg: &'a Type,
tcx: TyCtxt<'tcx>,
recurse: usize,
res: &mut Vec<RenderType>,
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
is_return: bool,
cache: &Cache,
) {
fn insert_ty(res: &mut Vec<RenderType>, ty: Type, mut generics: Vec<RenderType>) {
// generics and impl trait are both identified by their generics,
// rather than a type name itself
let anonymous = ty.is_full_generic() || ty.is_impl_trait();
let generics_empty = generics.is_empty();
if anonymous {
if generics_empty {
// This is a type parameter with no trait bounds (for example: `T` in
// `fn f<T>(p: T)`, so not useful for the rustdoc search because we would end up
// with an empty type with an empty name. Let's just discard it.
return;
} else if generics.len() == 1 {
// In this case, no need to go through an intermediate state if the type parameter
// contains only one trait bound.
//
// For example:
//
// `fn foo<T: Display>(r: Option<T>) {}`
//
// In this case, it would contain:
//
// ```
// [{
// name: "option",
// generics: [{
// name: "",
// generics: [
// name: "Display",
// generics: []
// }]
// }]
// }]
// ```
//
// After removing the intermediate (unnecessary) type parameter, it'll become:
//
// ```
// [{
// name: "option",
// generics: [{
// name: "Display",
// generics: []
// }]
// }]
// ```
//
// To be noted that it can work if there is ONLY ONE trait bound, otherwise we still
// need to keep it as is!
res.push(generics.pop().unwrap());
return;
}
}
let index_ty = get_index_type(&ty, generics);
if index_ty.id.is_none() && generics_empty {
return;
}
res.push(index_ty);
}
if recurse >= 10 {
// FIXME: remove this whole recurse thing when the recursion bug is fixed
// See #59502 for the original issue.
@ -548,88 +512,126 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>(
// for its bounds.
if let Type::Generic(arg_s) = *arg {
// First we check if the bounds are in a `where` predicate...
let mut type_bounds = Vec::new();
for where_pred in generics.where_predicates.iter().filter(|g| match g {
WherePredicate::BoundPredicate { ty: Type::Generic(ty_s), .. } => *ty_s == arg_s,
_ => false,
}) {
let mut ty_generics = Vec::new();
let bounds = where_pred.get_bounds().unwrap_or_else(|| &[]);
for bound in bounds.iter() {
if let Some(path) = bound.get_trait_path() {
let ty = Type::Path { path };
add_generics_and_bounds_as_types(
simplify_fn_type(
self_,
generics,
&ty,
tcx,
recurse + 1,
&mut ty_generics,
&mut type_bounds,
rgen,
is_return,
cache,
);
}
}
insert_ty(res, arg.clone(), ty_generics);
}
// Otherwise we check if the trait bounds are "inlined" like `T: Option<u32>`...
if let Some(bound) = generics.params.iter().find(|g| g.is_type() && g.name == arg_s) {
let mut ty_generics = Vec::new();
for bound in bound.get_bounds().unwrap_or(&[]) {
if let Some(path) = bound.get_trait_path() {
let ty = Type::Path { path };
add_generics_and_bounds_as_types(
simplify_fn_type(
self_,
generics,
&ty,
tcx,
recurse + 1,
&mut ty_generics,
&mut type_bounds,
rgen,
is_return,
cache,
);
}
}
insert_ty(res, arg.clone(), ty_generics);
}
if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) {
res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None });
} else {
let idx = -isize::try_from(rgen.len() + 1).unwrap();
rgen.insert(SimplifiedParam::Symbol(arg_s), (idx, type_bounds));
res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
}
} else if let Type::ImplTrait(ref bounds) = *arg {
let mut ty_generics = Vec::new();
let mut type_bounds = Vec::new();
for bound in bounds {
if let Some(path) = bound.get_trait_path() {
let ty = Type::Path { path };
add_generics_and_bounds_as_types(
simplify_fn_type(
self_,
generics,
&ty,
tcx,
recurse + 1,
&mut ty_generics,
&mut type_bounds,
rgen,
is_return,
cache,
);
}
}
insert_ty(res, arg.clone(), ty_generics);
if is_return && !type_bounds.is_empty() {
// In parameter position, `impl Trait` is a unique thing.
res.push(RenderType { id: None, generics: Some(type_bounds) });
} else {
// In parameter position, `impl Trait` is the same as an unnamed generic parameter.
let idx = -isize::try_from(rgen.len() + 1).unwrap();
rgen.insert(SimplifiedParam::Anonymous(idx), (idx, type_bounds));
res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
}
} else if let Type::Slice(ref ty) = *arg {
let mut ty_generics = Vec::new();
add_generics_and_bounds_as_types(
simplify_fn_type(
self_,
generics,
&ty,
tcx,
recurse + 1,
&mut ty_generics,
rgen,
is_return,
cache,
);
insert_ty(res, arg.clone(), ty_generics);
res.push(get_index_type(arg, ty_generics));
} else if let Type::Array(ref ty, _) = *arg {
let mut ty_generics = Vec::new();
add_generics_and_bounds_as_types(
simplify_fn_type(
self_,
generics,
&ty,
tcx,
recurse + 1,
&mut ty_generics,
rgen,
is_return,
cache,
);
insert_ty(res, arg.clone(), ty_generics);
res.push(get_index_type(arg, ty_generics));
} else if let Type::Tuple(ref tys) = *arg {
let mut ty_generics = Vec::new();
for ty in tys {
simplify_fn_type(
self_,
generics,
&ty,
tcx,
recurse + 1,
&mut ty_generics,
rgen,
is_return,
cache,
);
}
res.push(get_index_type(arg, 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.
@ -639,18 +641,26 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>(
let mut ty_generics = Vec::new();
if let Some(arg_generics) = arg.generics() {
for gen in arg_generics.iter() {
add_generics_and_bounds_as_types(
simplify_fn_type(
self_,
generics,
gen,
tcx,
recurse + 1,
&mut ty_generics,
rgen,
is_return,
cache,
);
}
}
insert_ty(res, arg.clone(), ty_generics);
let id = get_index_type_id(&arg);
if id.is_some() || !ty_generics.is_empty() {
res.push(RenderType {
id,
generics: if ty_generics.is_empty() { None } else { Some(ty_generics) },
});
}
}
}
@ -663,7 +673,7 @@ fn get_fn_inputs_and_outputs<'tcx>(
tcx: TyCtxt<'tcx>,
impl_generics: Option<&(clean::Type, clean::Generics)>,
cache: &Cache,
) -> (Vec<RenderType>, Vec<RenderType>) {
) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) {
let decl = &func.decl;
let combined_generics;
@ -689,21 +699,27 @@ fn get_fn_inputs_and_outputs<'tcx>(
(None, &func.generics)
};
let mut all_types = Vec::new();
let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
let mut arg_types = Vec::new();
for arg in decl.inputs.values.iter() {
let mut args = Vec::new();
add_generics_and_bounds_as_types(self_, generics, &arg.type_, tcx, 0, &mut args, cache);
if !args.is_empty() {
all_types.extend(args);
} else {
all_types.push(get_index_type(&arg.type_, vec![]));
}
simplify_fn_type(
self_,
generics,
&arg.type_,
tcx,
0,
&mut arg_types,
&mut rgen,
false,
cache,
);
}
let mut ret_types = Vec::new();
add_generics_and_bounds_as_types(self_, generics, &decl.output, tcx, 0, &mut ret_types, cache);
if ret_types.is_empty() {
ret_types.push(get_index_type(&decl.output, vec![]));
}
(all_types, ret_types)
simplify_fn_type(self_, generics, &decl.output, tcx, 0, &mut ret_types, &mut rgen, true, cache);
let mut simplified_params = rgen.into_values().collect::<Vec<_>>();
simplified_params.sort_by_key(|(idx, _)| -idx);
(arg_types, ret_types, simplified_params.into_iter().map(|(_idx, traits)| traits).collect())
}

View file

@ -9,7 +9,7 @@ function initSearch(searchIndex){}
/**
* @typedef {{
* name: string,
* id: integer,
* id: integer|null,
* fullPath: Array<string>,
* pathWithoutLast: Array<string>,
* pathLast: string,
@ -37,6 +37,7 @@ let ParserState;
* args: Array<QueryElement>,
* returned: Array<QueryElement>,
* foundElems: number,
* totalElems: number,
* literalSearch: boolean,
* corrections: Array<{from: string, to: integer}>,
* }}
@ -103,7 +104,7 @@ let ResultObject;
*
* fn something() -> Result<usize, usize>
*
* If output was allowed to be any RawFunctionType, it would look like this
* If output was allowed to be any RawFunctionType, it would look like thi
*
* [[], [50, [3, 3]]]
*
@ -113,10 +114,56 @@ let ResultObject;
* in favor of the pair of types interpretation. This is why the `(number|Array<RawFunctionType>)`
* is used instead of `(RawFunctionType|Array<RawFunctionType>)`.
*
* The output can be skipped if it's actually unit and there's no type constraints. If thi
* function accepts constrained generics, then the output will be unconditionally emitted, and
* after it will come a list of trait constraints. The position of the item in the list will
* determine which type parameter it is. For example:
*
* [1, 2, 3, 4, 5]
* ^ ^ ^ ^ ^
* | | | | - generic parameter (-3) of trait 5
* | | | - generic parameter (-2) of trait 4
* | | - generic parameter (-1) of trait 3
* | - this function returns a single value (type 2)
* - this function takes a single input parameter (type 1)
*
* Or, for a less contrived version:
*
* [[[4, -1], 3], [[5, -1]], 11]
* -^^^^^^^---- ^^^^^^^ ^^
* | | | - generic parameter, roughly `where -1: 11`
* | | | since -1 is the type parameter and 11 the trait
* | | - function output 5<-1>
* | - the overall function signature is something like
* | `fn(4<-1>, 3) -> 5<-1> where -1: 11`
* - function input, corresponds roughly to 4<-1>
* 4 is an index into the `p` array for a type
* -1 is the generic parameter, given by 11
*
* If a generic parameter has multiple trait constraints, it gets wrapped in an array, just like
* function inputs and outputs:
*
* [-1, -1, [4, 3]]
* ^^^^^^ where -1: 4 + 3
*
* If a generic parameter's trait constraint has generic parameters, it gets wrapped in the array
* even if only one exists. In other words, the ambiguity of `4<3>` and `4 + 3` is resolved in
* favor of `4 + 3`:
*
* [-1, -1, [[4, 3]]]
* ^^^^^^^^ where -1: 4 + 3
*
* [-1, -1, [5, [4, 3]]]
* ^^^^^^^^^^^ where -1: 5, -2: 4 + 3
*
* If a generic parameter has no trait constraints (like in Rust, the `Sized` constraint i
* implied and a fake `?Sized` constraint used to note its absence), it will be filled in with 0.
*
* @typedef {(
* 0 |
* [(number|Array<RawFunctionType>)] |
* [(number|Array<RawFunctionType>), (number|Array<RawFunctionType>)]
* [(number|Array<RawFunctionType>), (number|Array<RawFunctionType>)] |
* Array<(number|Array<RawFunctionType>)>
* )}
*/
let RawFunctionSearchType;
@ -136,6 +183,7 @@ let RawFunctionType;
* @typedef {{
* inputs: Array<FunctionType>,
* output: Array<FunctionType>,
* where_clause: Array<Array<FunctionType>>,
* }}
*/
let FunctionSearchType;

View file

@ -3,6 +3,17 @@
"use strict";
// polyfill
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced
if (!Array.prototype.toSpliced) {
// Can't use arrow functions, because we want `this`
Array.prototype.toSpliced = function() {
const me = this.slice();
Array.prototype.splice.apply(me, arguments);
return me;
};
}
(function() {
// This mapping table should match the discriminants of
// `rustdoc::formats::item_type::ItemType` type in Rust.
@ -33,6 +44,7 @@ const itemTypes = [
"attr",
"derive",
"traitalias",
"generic",
];
const longItemTypes = [
@ -67,6 +79,7 @@ const longItemTypes = [
// used for special search precedence
const TY_PRIMITIVE = itemTypes.indexOf("primitive");
const TY_KEYWORD = itemTypes.indexOf("keyword");
const TY_GENERIC = itemTypes.indexOf("generic");
const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
function hasOwnPropertyRustdoc(obj, property) {
@ -252,7 +265,7 @@ function initSearch(rawSearchIndex) {
/**
* Add an item to the type Name->ID map, or, if one already exists, use it.
* Returns the number. If name is "" or null, return -1 (pure generic).
* Returns the number. If name is "" or null, return null (pure generic).
*
* This is effectively string interning, so that function matching can be
* done more quickly. Two types with the same name but different item kinds
@ -264,7 +277,7 @@ function initSearch(rawSearchIndex) {
*/
function buildTypeMapIndex(name) {
if (name === "" || name === null) {
return -1;
return null;
}
if (typeNameIdMap.has(name)) {
@ -489,7 +502,7 @@ function initSearch(rawSearchIndex) {
}
return {
name: "never",
id: -1,
id: null,
fullPath: ["never"],
pathWithoutLast: [],
pathLast: "never",
@ -531,7 +544,7 @@ function initSearch(rawSearchIndex) {
}
return {
name: name.trim(),
id: -1,
id: null,
fullPath: pathSegments,
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
pathLast: pathSegments[pathSegments.length - 1],
@ -660,7 +673,7 @@ function initSearch(rawSearchIndex) {
}
elems.push({
name: "[]",
id: -1,
id: null,
fullPath: ["[]"],
pathWithoutLast: [],
pathLast: "[]",
@ -971,9 +984,13 @@ function initSearch(rawSearchIndex) {
returned: [],
// Total number of "top" elements (does not include generics).
foundElems: 0,
// Total number of elements (includes generics).
totalElems: 0,
literalSearch: false,
error: null,
correction: null,
proposeCorrectionFrom: null,
proposeCorrectionTo: null,
};
}
@ -1014,64 +1031,10 @@ function initSearch(rawSearchIndex) {
/**
* Parses the query.
*
* The supported syntax by this parser is as follow:
* The supported syntax by this parser is given in the rustdoc book chapter
* /src/doc/rustdoc/src/read-documentation/search.md
*
* ident = *(ALPHA / DIGIT / "_")
* path = ident *(DOUBLE-COLON/{WS} ident) [!]
* slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
* arg = [type-filter *WS COLON *WS] (path [generics] / slice)
* type-sep = *WS COMMA *(COMMA)
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
* 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 ]
* type-search = [ nonempty-arg-list ] [ return-args ]
*
* query = *WS (exact-search / type-search) *WS
*
* type-filter = (
* "mod" /
* "externcrate" /
* "import" /
* "struct" /
* "enum" /
* "fn" /
* "type" /
* "static" /
* "trait" /
* "impl" /
* "tymethod" /
* "method" /
* "structfield" /
* "variant" /
* "macro" /
* "primitive" /
* "associatedtype" /
* "constant" /
* "associatedconstant" /
* "union" /
* "foreigntype" /
* "keyword" /
* "existential" /
* "attr" /
* "derive" /
* "traitalias")
*
* OPEN-ANGLE-BRACKET = "<"
* CLOSE-ANGLE-BRACKET = ">"
* OPEN-SQUARE-BRACKET = "["
* CLOSE-SQUARE-BRACKET = "]"
* COLON = ":"
* DOUBLE-COLON = "::"
* QUOTE = %x22
* COMMA = ","
* RETURN-ARROW = "->"
*
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
* DIGIT = %x30-39
* WS = %x09 / " "
* When adding new things to the parser, add them there, too!
*
* @param {string} val - The user query
*
@ -1124,6 +1087,7 @@ function initSearch(rawSearchIndex) {
query.literalSearch = parserState.totalElems > 1;
}
query.foundElems = query.elems.length + query.returned.length;
query.totalElems = parserState.totalElems;
return query;
}
@ -1172,7 +1136,7 @@ function initSearch(rawSearchIndex) {
const out = [];
for (const result of results) {
if (result.id > -1) {
if (result.id !== -1) {
const obj = searchIndex[result.id];
obj.dist = result.dist;
const res = buildHrefAndPath(obj);
@ -1348,192 +1312,311 @@ function initSearch(rawSearchIndex) {
* This function checks generics in search query `queryElem` can all be found in the
* search index (`fnType`),
*
* @param {FunctionType} fnType - The object to check.
* @param {QueryElement} queryElem - The element from the parsed query.
* This function returns `true` if it matches, and also writes the results to mgensInout.
* It returns `false` if no match is found, and leaves mgensInout untouched.
*
* @param {FunctionType} fnType - The object to check.
* @param {QueryElement} queryElem - The element from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgensInout - Map functions generics to query generics.
*
* @return {boolean} - Returns true if a match, false otherwise.
*/
function checkGenerics(fnType, queryElem) {
return unifyFunctionTypes(fnType.generics, queryElem.generics);
function checkGenerics(fnType, queryElem, whereClause, mgensInout) {
return unifyFunctionTypes(
fnType.generics,
queryElem.generics,
whereClause,
mgensInout,
mgens => {
if (mgensInout) {
for (const [fid, qid] of mgens.entries()) {
mgensInout.set(fid, qid);
}
}
return true;
}
);
}
/**
* This function checks if a list of search query `queryElems` can all be found in the
* search index (`fnTypes`).
*
* @param {Array<FunctionType>} fnTypes - The objects to check.
* This function returns `true` on a match, or `false` if none. If `solutionCb` is
* supplied, it will call that function with mgens, and that callback can accept or
* reject the result bu returning `true` or `false`. If the callback returns false,
* then this function will try with a different solution, or bail with false if it
* runs out of candidates.
*
* @param {Array<FunctionType>} fnTypes - The objects to check.
* @param {Array<QueryElement>} queryElems - The elements from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgensIn
* - Map functions generics to query generics (never modified).
* @param {null|Map<number,number> -> bool} solutionCb - Called for each `mgens` solution.
*
* @return {boolean} - Returns true if a match, false otherwise.
*/
function unifyFunctionTypes(fnTypes, queryElems) {
// This search engine implements order-agnostic unification. There
// should be no missing duplicates (generics have "bag semantics"),
// and the row is allowed to have extras.
function unifyFunctionTypes(fnTypesIn, queryElems, whereClause, mgensIn, solutionCb) {
/**
* @type Map<integer, integer>
*/
let mgens = new Map(mgensIn);
if (queryElems.length === 0) {
return true;
return !solutionCb || solutionCb(mgens);
}
if (!fnTypes || fnTypes.length === 0) {
if (!fnTypesIn || fnTypesIn.length === 0) {
return false;
}
const ql = queryElems.length;
let fl = fnTypesIn.length;
/**
* @type Map<integer, QueryElement[]>
* @type Array<FunctionType>
*/
const queryElemSet = new Map();
const addQueryElemToQueryElemSet = queryElem => {
let currentQueryElemList;
if (queryElemSet.has(queryElem.id)) {
currentQueryElemList = queryElemSet.get(queryElem.id);
} else {
currentQueryElemList = [];
queryElemSet.set(queryElem.id, currentQueryElemList);
}
currentQueryElemList.push(queryElem);
};
for (const queryElem of queryElems) {
addQueryElemToQueryElemSet(queryElem);
}
let fnTypes = fnTypesIn.slice();
/**
* @type Map<integer, FunctionType[]>
* loop works by building up a solution set in the working arrays
* fnTypes gets mutated in place to make this work, while queryElems
* is left alone
*
* vvvvvvv `i` points here
* queryElems = [ good, good, good, unknown, unknown ],
* fnTypes = [ good, good, good, unknown, unknown ],
* ---------------- ^^^^^^^^^^^^^^^^ `j` iterates after `i`,
* | looking for candidates
* everything before `i` is the
* current working solution
*
* Everything in the current working solution is known to be a good
* match, but it might not be the match we wind up going with, because
* there might be more than one candidate match, and we need to try them all
* before giving up. So, to handle this, it backtracks on failure.
*
* @type Array<{
* "fnTypesScratch": Array<FunctionType>,
* "queryElemsOffset": integer,
* "fnTypesOffset": integer
* }>
*/
const fnTypeSet = new Map();
const addFnTypeToFnTypeSet = fnType => {
// Pure generic, or an item that's not matched by any query elems.
// Try [unboxing] it.
//
// [unboxing]:
// http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
if (fnType.id === -1 || !(
queryElemSet.has(fnType.id) ||
(fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
(fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem)
)) {
for (const innerFnType of fnType.generics) {
addFnTypeToFnTypeSet(innerFnType);
const backtracking = [];
let i = 0;
let j = 0;
const backtrack = () => {
while (backtracking.length !== 0) {
// this session failed, but there are other possible solutions
// to backtrack, reset to (a copy of) the old array, do the swap or unboxing
const {
fnTypesScratch,
mgensScratch,
queryElemsOffset,
fnTypesOffset,
unbox,
} = backtracking.pop();
mgens = new Map(mgensScratch);
const fnType = fnTypesScratch[fnTypesOffset];
const queryElem = queryElems[queryElemsOffset];
if (unbox) {
if (fnType.id < 0) {
if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
continue;
}
mgens.set(fnType.id, 0);
}
const generics = fnType.id < 0 ?
whereClause[(-fnType.id) - 1] :
fnType.generics;
fnTypes = fnTypesScratch.toSpliced(fnTypesOffset, 1, ...generics);
fl = fnTypes.length;
// re-run the matching algorithm on this item
i = queryElemsOffset - 1;
} else {
if (fnType.id < 0) {
if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) {
continue;
}
mgens.set(fnType.id, queryElem.id);
}
fnTypes = fnTypesScratch.slice();
fl = fnTypes.length;
const tmp = fnTypes[queryElemsOffset];
fnTypes[queryElemsOffset] = fnTypes[fnTypesOffset];
fnTypes[fnTypesOffset] = tmp;
// this is known as a good match; go to the next one
i = queryElemsOffset;
}
return;
}
let currentQueryElemList = queryElemSet.get(fnType.id) || [];
let matchIdx = currentQueryElemList.findIndex(queryElem => {
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
checkGenerics(fnType, queryElem);
});
if (matchIdx === -1 &&
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) &&
queryContainsArrayOrSliceElem
) {
currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || [];
matchIdx = currentQueryElemList.findIndex(queryElem => {
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
checkGenerics(fnType, queryElem);
});
}
// None of the query elems match the function type.
// Try [unboxing] it.
if (matchIdx === -1) {
for (const innerFnType of fnType.generics) {
addFnTypeToFnTypeSet(innerFnType);
}
return;
}
let currentFnTypeList;
if (fnTypeSet.has(fnType.id)) {
currentFnTypeList = fnTypeSet.get(fnType.id);
} else {
currentFnTypeList = [];
fnTypeSet.set(fnType.id, currentFnTypeList);
}
currentFnTypeList.push(fnType);
};
for (const fnType of fnTypes) {
addFnTypeToFnTypeSet(fnType);
}
const doHandleQueryElemList = (currentFnTypeList, queryElemList) => {
if (queryElemList.length === 0) {
return true;
}
// Multiple items in one list might match multiple items in another.
// Since an item with fewer generics can match an item with more, we
// need to check all combinations for a potential match.
const queryElem = queryElemList.pop();
const l = currentFnTypeList.length;
for (let i = 0; i < l; i += 1) {
const fnType = currentFnTypeList[i];
if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
continue;
}
const queryElemPathLength = queryElem.pathWithoutLast.length;
// If the query element is a path (it contains `::`), we need to check if this
// path is compatible with the target type.
if (queryElemPathLength > 0) {
const fnTypePath = fnType.path !== undefined && fnType.path !== null ?
fnType.path.split("::") : [];
// If the path provided in the query element is longer than this type,
// no need to check it since it won't match in any case.
if (queryElemPathLength > fnTypePath.length) {
continue;
}
let i = 0;
for (const path of fnTypePath) {
if (path === queryElem.pathWithoutLast[i]) {
i += 1;
if (i >= queryElemPathLength) {
break;
}
}
}
if (i < queryElemPathLength) {
// If we didn't find all parts of the path of the query element inside
// the fn type, then it's not the right one.
continue;
}
}
if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) {
currentFnTypeList.splice(i, 1);
const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
if (result) {
return true;
}
currentFnTypeList.splice(i, 0, fnType);
}
}
return false;
};
const handleQueryElemList = (id, queryElemList) => {
if (!fnTypeSet.has(id)) {
if (id === typeNameIdOfArrayOrSlice) {
return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
handleQueryElemList(typeNameIdOfArray, queryElemList);
for (i = 0; i !== ql; ++i) {
const queryElem = queryElems[i];
/**
* list of potential function types that go with the current query element.
* @type Array<integer>
*/
const matchCandidates = [];
let fnTypesScratch = null;
let mgensScratch = null;
// don't try anything before `i`, because they've already been
// paired off with the other query elements
for (j = i; j !== fl; ++j) {
const fnType = fnTypes[j];
if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
if (!fnTypesScratch) {
fnTypesScratch = fnTypes.slice();
}
unifyFunctionTypes(
fnType.generics,
queryElem.generics,
whereClause,
mgens,
mgensScratch => {
matchCandidates.push({
fnTypesScratch,
mgensScratch,
queryElemsOffset: i,
fnTypesOffset: j,
unbox: false,
});
return false; // "reject" all candidates to gather all of them
}
);
}
return false;
}
const currentFnTypeList = fnTypeSet.get(id);
if (currentFnTypeList.length < queryElemList.length) {
// It's not possible for all the query elems to find a match.
return false;
}
const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
if (result) {
// Found a solution.
// Any items that weren't used for it can be unboxed, and might form
// part of the solution for another item.
for (const innerFnType of currentFnTypeList) {
addFnTypeToFnTypeSet(innerFnType);
if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
if (!fnTypesScratch) {
fnTypesScratch = fnTypes.slice();
}
if (!mgensScratch) {
mgensScratch = new Map(mgens);
}
backtracking.push({
fnTypesScratch,
mgensScratch,
queryElemsOffset: i,
fnTypesOffset: j,
unbox: true,
});
}
fnTypeSet.delete(id);
}
return result;
};
let queryElemSetSize = -1;
while (queryElemSetSize !== queryElemSet.size) {
queryElemSetSize = queryElemSet.size;
for (const [id, queryElemList] of queryElemSet) {
if (handleQueryElemList(id, queryElemList)) {
queryElemSet.delete(id);
if (matchCandidates.length === 0) {
if (backtrack()) {
continue;
} else {
return false;
}
}
// use the current candidate
const {fnTypesOffset: candidate, mgensScratch: mgensNew} = matchCandidates.pop();
if (fnTypes[candidate].id < 0 && queryElems[i].id < 0) {
mgens.set(fnTypes[candidate].id, queryElems[i].id);
}
for (const [fid, qid] of mgensNew) {
mgens.set(fid, qid);
}
// `i` and `j` are paired off
// `queryElems[i]` is left in place
// `fnTypes[j]` is swapped with `fnTypes[i]` to pair them off
const tmp = fnTypes[candidate];
fnTypes[candidate] = fnTypes[i];
fnTypes[i] = tmp;
// write other candidates to backtracking queue
for (const otherCandidate of matchCandidates) {
backtracking.push(otherCandidate);
}
// If we're on the last item, check the solution with the callback
// backtrack if the callback says its unsuitable
while (i === (ql - 1) && solutionCb && !solutionCb(mgens)) {
if (!backtrack()) {
return false;
}
}
}
return queryElemSetSize === 0;
return true;
}
function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens) {
// type filters look like `trait:Read` or `enum:Result`
if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
return false;
}
// fnType.id < 0 means generic
// queryElem.id < 0 does too
// mgens[fnType.id] = queryElem.id
// or, if mgens[fnType.id] = 0, then we've matched this generic with a bare trait
// and should make that same decision everywhere it appears
if (fnType.id < 0 && queryElem.id < 0) {
if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) {
return false;
}
for (const [fid, qid] of mgens.entries()) {
if (fnType.id !== fid && queryElem.id === qid) {
return false;
}
if (fnType.id === fid && queryElem.id !== qid) {
return false;
}
}
} else if (fnType.id !== null) {
if (queryElem.id === typeNameIdOfArrayOrSlice &&
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray)
) {
// [] matches primitive:array or primitive:slice
// if it matches, then we're fine, and this is an appropriate match candidate
} else if (fnType.id !== queryElem.id) {
return false;
}
// If the query elem has generics, and the function doesn't,
// it can't match.
if (fnType.generics.length === 0 && queryElem.generics.length !== 0) {
return false;
}
// If the query element is a path (it contains `::`), we need to check if this
// path is compatible with the target type.
const queryElemPathLength = queryElem.pathWithoutLast.length;
if (queryElemPathLength > 0) {
const fnTypePath = fnType.path !== undefined && fnType.path !== null ?
fnType.path.split("::") : [];
// If the path provided in the query element is longer than this type,
// no need to check it since it won't match in any case.
if (queryElemPathLength > fnTypePath.length) {
return false;
}
let i = 0;
for (const path of fnTypePath) {
if (path === queryElem.pathWithoutLast[i]) {
i += 1;
if (i >= queryElemPathLength) {
break;
}
}
}
if (i < queryElemPathLength) {
// If we didn't find all parts of the path of the query element inside
// the fn type, then it's not the right one.
return false;
}
}
}
return true;
}
function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) {
if (fnType.id < 0 && queryElem.id >= 0) {
if (!whereClause) {
return false;
}
// mgens[fnType.id] === 0 indicates that we committed to unboxing this generic
// mgens[fnType.id] === null indicates that we haven't decided yet
if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
return false;
}
// This is only a potential unbox if the search query appears in the where clause
// for example, searching `Read -> usize` should find
// `fn read_all<R: Read>(R) -> Result<usize>`
// generic `R` is considered "unboxed"
return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause);
} else if (fnType.generics && fnType.generics.length > 0) {
return checkIfInList(fnType.generics, queryElem, whereClause);
}
return false;
}
/**
@ -1541,13 +1624,14 @@ function initSearch(rawSearchIndex) {
* generics (if any).
*
* @param {Array<FunctionType>} list
* @param {QueryElement} elem - The element from the parsed query.
* @param {QueryElement} elem - The element from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
*
* @return {boolean} - Returns true if found, false otherwise.
*/
function checkIfInList(list, elem) {
function checkIfInList(list, elem, whereClause) {
for (const entry of list) {
if (checkType(entry, elem)) {
if (checkType(entry, elem, whereClause)) {
return true;
}
}
@ -1559,14 +1643,26 @@ function initSearch(rawSearchIndex) {
* generics (if any).
*
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
* @param {QueryElement} elem - The element from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
*
* @return {boolean} - Returns true if the type matches, false otherwise.
*/
function checkType(row, elem) {
if (row.id === -1) {
function checkType(row, elem, whereClause) {
if (row.id === null) {
// This is a pure "generic" search, no need to run other checks.
return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false;
return row.generics.length > 0
? checkIfInList(row.generics, elem, whereClause)
: false;
}
if (row.id < 0 && elem.id >= 0) {
const gid = (-row.id) - 1;
return checkIfInList(whereClause[gid], elem, whereClause);
}
if (row.id < 0 && elem.id < 0) {
return true;
}
const matchesExact = row.id === elem.id;
@ -1576,7 +1672,7 @@ function initSearch(rawSearchIndex) {
if ((matchesExact || matchesArrayOrSlice) &&
typePassesFilter(elem.typeFilter, row.ty)) {
if (elem.generics.length > 0) {
return checkGenerics(row, elem);
return checkGenerics(row, elem, whereClause, new Map());
}
return true;
}
@ -1584,7 +1680,7 @@ function initSearch(rawSearchIndex) {
// If the current item does not match, try [unboxing] the generic.
// [unboxing]:
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
return checkIfInList(row.generics, elem);
return checkIfInList(row.generics, elem, whereClause);
}
function checkPath(contains, ty, maxEditDistance) {
@ -1785,13 +1881,15 @@ function initSearch(rawSearchIndex) {
const fullId = row.id;
const searchWord = searchWords[pos];
const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem);
const in_args = row.type && row.type.inputs
&& checkIfInList(row.type.inputs, elem, row.type.where_clause);
if (in_args) {
// path_dist is 0 because no parent path information is currently stored
// in the search index
addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
}
const returned = row.type && row.type.output && checkIfInList(row.type.output, elem);
const returned = row.type && row.type.output
&& checkIfInList(row.type.output, elem, row.type.where_clause);
if (returned) {
addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
}
@ -1853,10 +1951,20 @@ function initSearch(rawSearchIndex) {
}
// If the result is too "bad", we return false and it ends this search.
if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) {
return;
}
if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) {
if (!unifyFunctionTypes(
row.type.inputs,
parsedQuery.elems,
row.type.where_clause,
null,
mgens => {
return unifyFunctionTypes(
row.type.output,
parsedQuery.returned,
row.type.where_clause,
mgens
);
}
)) {
return;
}
@ -1875,6 +1983,11 @@ function initSearch(rawSearchIndex) {
}
const maxEditDistance = Math.floor(queryLen / 3);
/**
* @type {Map<string, integer>}
*/
const genericSymbols = new Map();
/**
* Convert names to ids in parsed query elements.
* This is not used for the "In Names" tab, but is used for the
@ -1891,7 +2004,7 @@ function initSearch(rawSearchIndex) {
if (typeNameIdMap.has(elem.pathLast)) {
elem.id = typeNameIdMap.get(elem.pathLast);
} else if (!parsedQuery.literalSearch) {
let match = -1;
let match = null;
let matchDist = maxEditDistance + 1;
let matchName = "";
for (const [name, id] of typeNameIdMap) {
@ -1905,11 +2018,52 @@ function initSearch(rawSearchIndex) {
matchName = name;
}
}
if (match !== -1) {
if (match !== null) {
parsedQuery.correction = matchName;
}
elem.id = match;
}
if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1
&& elem.generics.length === 0)
|| elem.typeFilter === TY_GENERIC) {
if (genericSymbols.has(elem.name)) {
elem.id = genericSymbols.get(elem.name);
} else {
elem.id = -(genericSymbols.size + 1);
genericSymbols.set(elem.name, elem.id);
}
if (elem.typeFilter === -1 && elem.name.length >= 3) {
// Silly heuristic to catch if the user probably meant
// to not write a generic parameter. We don't use it,
// just bring it up.
const maxPartDistance = Math.floor(elem.name.length / 3);
let matchDist = maxPartDistance + 1;
let matchName = "";
for (const name of typeNameIdMap.keys()) {
const dist = editDistance(name, elem.name, maxPartDistance);
if (dist <= matchDist && dist <= maxPartDistance) {
if (dist === matchDist && matchName > name) {
continue;
}
matchDist = dist;
matchName = name;
}
}
if (matchName !== "") {
parsedQuery.proposeCorrectionFrom = elem.name;
parsedQuery.proposeCorrectionTo = matchName;
}
}
elem.typeFilter = TY_GENERIC;
}
if (elem.generics.length > 0 && elem.typeFilter === TY_GENERIC) {
// Rust does not have HKT
parsedQuery.error = [
"Generic type parameter ",
elem.name,
" does not accept generic parameters",
];
}
for (const elem2 of elem.generics) {
convertNameToId(elem2);
}
@ -1943,8 +2097,11 @@ function initSearch(rawSearchIndex) {
elem = parsedQuery.returned[0];
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
row = searchIndex[i];
in_returned = row.type &&
unifyFunctionTypes(row.type.output, parsedQuery.returned);
in_returned = row.type && unifyFunctionTypes(
row.type.output,
parsedQuery.returned,
row.type.where_clause
);
if (in_returned) {
addIntoResults(
results_others,
@ -2295,6 +2452,13 @@ ${item.displayPath}<span class="${type}">${name}</span>\
"Showing results for closest type name " +
`"${results.query.correction}" instead.</h3>`;
}
if (results.query.proposeCorrectionFrom !== null) {
const orig = results.query.proposeCorrectionFrom;
const targ = results.query.proposeCorrectionTo;
output += "<h3 class=\"search-corrections\">" +
`Type "${orig}" not found and used as generic parameter. ` +
`Consider searching for "${targ}" instead.</h3>`;
}
const resultsElem = document.createElement("div");
resultsElem.id = "results";
@ -2396,37 +2560,54 @@ ${item.displayPath}<span class="${type}">${name}</span>\
* @return {Array<FunctionSearchType>}
*/
function buildItemSearchTypeAll(types, lowercasePaths) {
return types.map(type => buildItemSearchType(type, lowercasePaths));
}
/**
* Converts a single type.
*
* @param {RawFunctionType} type
*/
function buildItemSearchType(type, lowercasePaths) {
const PATH_INDEX_DATA = 0;
const GENERICS_DATA = 1;
return types.map(type => {
let pathIndex, generics;
if (typeof type === "number") {
pathIndex = type;
generics = [];
} else {
pathIndex = type[PATH_INDEX_DATA];
generics = buildItemSearchTypeAll(
type[GENERICS_DATA],
lowercasePaths
);
}
// `0` is used as a sentinel because it's fewer bytes than `null`
if (pathIndex === 0) {
return {
id: -1,
ty: null,
path: null,
generics: generics,
};
}
const item = lowercasePaths[pathIndex - 1];
let pathIndex, generics;
if (typeof type === "number") {
pathIndex = type;
generics = [];
} else {
pathIndex = type[PATH_INDEX_DATA];
generics = buildItemSearchTypeAll(
type[GENERICS_DATA],
lowercasePaths
);
}
if (pathIndex < 0) {
// types less than 0 are generic parameters
// the actual names of generic parameters aren't stored, since they aren't API
return {
id: buildTypeMapIndex(item.name),
ty: item.ty,
path: item.path,
generics: generics,
id: pathIndex,
ty: TY_GENERIC,
path: null,
generics,
};
});
}
if (pathIndex === 0) {
// `0` is used as a sentinel because it's fewer bytes than `null`
return {
id: null,
ty: null,
path: null,
generics,
};
}
const item = lowercasePaths[pathIndex - 1];
return {
id: buildTypeMapIndex(item.name),
ty: item.ty,
path: item.path,
generics,
};
}
/**
@ -2454,23 +2635,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
}
let inputs, output;
if (typeof functionSearchType[INPUTS_DATA] === "number") {
const pathIndex = functionSearchType[INPUTS_DATA];
if (pathIndex === 0) {
inputs = [{
id: -1,
ty: null,
path: null,
generics: [],
}];
} else {
const item = lowercasePaths[pathIndex - 1];
inputs = [{
id: buildTypeMapIndex(item.name),
ty: item.ty,
path: item.path,
generics: [],
}];
}
inputs = [buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths)];
} else {
inputs = buildItemSearchTypeAll(
functionSearchType[INPUTS_DATA],
@ -2479,23 +2644,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
}
if (functionSearchType.length > 1) {
if (typeof functionSearchType[OUTPUT_DATA] === "number") {
const pathIndex = functionSearchType[OUTPUT_DATA];
if (pathIndex === 0) {
output = [{
id: -1,
ty: null,
path: null,
generics: [],
}];
} else {
const item = lowercasePaths[pathIndex - 1];
output = [{
id: buildTypeMapIndex(item.name),
ty: item.ty,
path: item.path,
generics: [],
}];
}
output = [buildItemSearchType(functionSearchType[OUTPUT_DATA], lowercasePaths)];
} else {
output = buildItemSearchTypeAll(
functionSearchType[OUTPUT_DATA],
@ -2505,8 +2654,15 @@ ${item.displayPath}<span class="${type}">${name}</span>\
} else {
output = [];
}
const where_clause = [];
const l = functionSearchType.length;
for (let i = 2; i < l; ++i) {
where_clause.push(typeof functionSearchType[i] === "number"
? [buildItemSearchType(functionSearchType[i], lowercasePaths)]
: buildItemSearchTypeAll(functionSearchType[i], lowercasePaths));
}
return {
inputs, output,
inputs, output, where_clause,
};
}

View file

@ -23,7 +23,9 @@ function contentToDiffLine(key, value) {
}
function shouldIgnoreField(fieldName) {
return fieldName === "query" || fieldName === "correction";
return fieldName === "query" || fieldName === "correction" ||
fieldName === "proposeCorrectionFrom" ||
fieldName === "proposeCorrectionTo";
}
// This function is only called when no matching result was found and therefore will only display

View file

@ -54,3 +54,53 @@ assert-text: (
".search-corrections",
"Type \"notablestructwithlongnamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
)
// Now, generic correction
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write: (".search-input", "NotableStructWithLongNamr, NotableStructWithLongNamr")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
assert-css: (".search-corrections", {
"display": "block"
})
assert-text: (
".search-corrections",
"Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
)
// Now, generic correction plus error
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write: (".search-input", "Foo<NotableStructWithLongNamr>,y")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
assert-css: (".search-corrections", {
"display": "block"
})
assert-text: (
".search-corrections",
"Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
)
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write: (".search-input", "generic:NotableStructWithLongNamr<x>,y")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
assert-css: (".error", {
"display": "block"
})
assert-text: (
".error",
"Query parser error: \"Generic type parameter notablestructwithlongnamr does not accept generic parameters\"."
)

View file

@ -1,3 +1,7 @@
// ignore-order
const FILTER_CRATE = "std";
const EXPECTED = [
{
'query': 'option, fnonce -> option',
@ -19,4 +23,62 @@ const EXPECTED = [
{ 'path': 'std::option::Option', 'name': 'as_mut_slice' },
],
},
{
'query': 'option<t>, option<t> -> option<t>',
'others': [
{ 'path': 'std::option::Option', 'name': 'or' },
{ 'path': 'std::option::Option', 'name': 'xor' },
],
},
{
'query': 'option<t>, option<u> -> option<u>',
'others': [
{ 'path': 'std::option::Option', 'name': 'and' },
{ 'path': 'std::option::Option', 'name': 'zip' },
],
},
{
'query': 'option<t>, option<u> -> option<t>',
'others': [
{ 'path': 'std::option::Option', 'name': 'and' },
{ 'path': 'std::option::Option', 'name': 'zip' },
],
},
{
'query': 'option<t>, option<u> -> option<t, u>',
'others': [
{ 'path': 'std::option::Option', 'name': 'zip' },
],
},
{
'query': 'option<t>, e -> result<t, e>',
'others': [
{ 'path': 'std::option::Option', 'name': 'ok_or' },
{ 'path': 'std::result::Result', 'name': 'transpose' },
],
},
{
'query': 'result<option<t>, e> -> option<result<t, e>>',
'others': [
{ 'path': 'std::result::Result', 'name': 'transpose' },
],
},
{
'query': 'option<t>, option<t> -> bool',
'others': [
{ 'path': 'std::option::Option', 'name': 'eq' },
],
},
{
'query': 'option<option<t>> -> option<t>',
'others': [
{ 'path': 'std::option::Option', 'name': 'flatten' },
],
},
{
'query': 'option<t>',
'returned': [
{ 'path': 'std::result::Result', 'name': 'ok' },
],
},
];

View file

@ -0,0 +1,22 @@
// ignore-order
const FILTER_CRATE = "std";
const EXPECTED = [
{
'query': 'vec::intoiter<T> -> [T]',
'others': [
{ 'path': 'std::vec::IntoIter', 'name': 'as_slice' },
{ 'path': 'std::vec::IntoIter', 'name': 'as_mut_slice' },
{ 'path': 'std::vec::IntoIter', 'name': 'next_chunk' },
],
},
{
'query': 'vec::intoiter<T> -> []',
'others': [
{ 'path': 'std::vec::IntoIter', 'name': 'as_slice' },
{ 'path': 'std::vec::IntoIter', 'name': 'as_mut_slice' },
{ 'path': 'std::vec::IntoIter', 'name': 'next_chunk' },
],
},
];

View file

@ -79,6 +79,7 @@ const EXPECTED = [
{ 'path': 'generics_match_ambiguity', 'name': 'baac' },
{ 'path': 'generics_match_ambiguity', 'name': 'baaf' },
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
},
{

View file

@ -12,11 +12,15 @@ const EXPECTED = [
],
},
{
'query': 'Result<SomeTraiz>',
'correction': null,
'query': 'Resulx<SomeTrait>',
'in_args': [],
'returned': [],
},
{
'query': 'Result<SomeTraiz>',
'proposeCorrectionFrom': 'SomeTraiz',
'proposeCorrectionTo': 'SomeTrait',
},
{
'query': 'OtherThingxxxxxxxx',
'correction': null,

View file

@ -0,0 +1,38 @@
// exact-check
const EXPECTED = [
{
'query': 'Inside<T> -> Out1<T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'alpha' },
],
},
{
'query': 'Inside<T> -> Out3<T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
{
'query': 'Inside<T> -> Out4<T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
{
'query': 'Inside<T> -> Out3<U, T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
{
'query': 'Inside<T> -> Out4<U, T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
];

View file

@ -0,0 +1,36 @@
pub struct Out<A, B = ()> {
a: A,
b: B,
}
pub struct Out1<A, const N: usize> {
a: [A; N],
}
pub struct Out2<A, const N: usize> {
a: [A; N],
}
pub struct Out3<A, B> {
a: A,
b: B,
}
pub struct Out4<A, B> {
a: A,
b: B,
}
pub struct Inside<T>(T);
pub fn alpha<const N: usize, T>(_: Inside<T>) -> Out<Out1<T, N>, Out2<T, N>> {
loop {}
}
pub fn beta<T, U>(_: Inside<T>) -> Out<Out3<T, U>, Out4<U, T>> {
loop {}
}
pub fn gamma<T, U>(_: Inside<T>) -> Out<Out3<U, T>, Out4<T, U>> {
loop {}
}

View file

@ -0,0 +1,87 @@
// exact-check
// ignore-order
const EXPECTED = [
{
query: '-> trait:Some',
others: [
{ path: 'foo', name: 'alef' },
{ path: 'foo', name: 'alpha' },
],
},
{
query: '-> generic:T',
others: [
{ path: 'foo', name: 'bet' },
{ path: 'foo', name: 'alef' },
{ path: 'foo', name: 'beta' },
],
},
{
query: 'A -> B',
others: [
{ path: 'foo', name: 'bet' },
],
},
{
query: 'A -> A',
others: [
{ path: 'foo', name: 'beta' },
],
},
{
query: 'A, A',
others: [
{ path: 'foo', name: 'alternate' },
],
},
{
query: 'A, B',
others: [
{ path: 'foo', name: 'other' },
],
},
{
query: 'Other, Other',
others: [
{ path: 'foo', name: 'other' },
{ path: 'foo', name: 'alternate' },
],
},
{
query: 'generic:T',
in_args: [
{ path: 'foo', name: 'bet' },
{ path: 'foo', name: 'beta' },
{ path: 'foo', name: 'other' },
{ path: 'foo', name: 'alternate' },
],
},
{
query: 'generic:Other',
in_args: [
{ path: 'foo', name: 'bet' },
{ path: 'foo', name: 'beta' },
{ path: 'foo', name: 'other' },
{ path: 'foo', name: 'alternate' },
],
},
{
query: 'trait:Other',
in_args: [
{ path: 'foo', name: 'other' },
{ path: 'foo', name: 'alternate' },
],
},
{
query: 'Other',
in_args: [
{ path: 'foo', name: 'other' },
{ path: 'foo', name: 'alternate' },
],
},
{
query: 'trait:T',
in_args: [],
},
];

View file

@ -0,0 +1,15 @@
#![crate_name="foo"]
pub trait Some {}
impl Some for () {}
pub trait Other {}
impl Other for () {}
pub fn alef<T: Some>() -> T { loop {} }
pub fn alpha() -> impl Some { }
pub fn bet<T, U>(t: T) -> U { loop {} }
pub fn beta<T>(t: T) -> T {}
pub fn other<T: Other, U: Other>(t: T, u: U) { loop {} }
pub fn alternate<T: Other>(t: T, u: T) { loop {} }