Rollup merge of #116085 - notriddle:notriddle/search-associated-types, r=GuillaumeGomez
rustdoc-search: add support for traits and associated types # Summary Trait associated type queries work in rustdoc's type driven search. The data is included in the search-index.js file, and the queries are designed to "do what I mean" when users type them in, so, for example, `Iterator<Item=T> -> Option<T>` includes `Iterator::next` in the SERP[^SERP], and `Iterator<T> -> Option<T>` also includes `Iterator::next` in the SERP. [^SERP]: search engine results page ## Sample searches * [`iterator<Item=T>, fnmut -> T`][iterreduce] * [`iterator<T>, fnmut -> T`][iterreduceterse] [iterreduce]: http://notriddle.com/rustdoc-html-demo-5/associated-types/std/index.html?search=iterator%3CItem%3DT%3E%2C%20fnmut%20-%3E%20T&filter-crate=std [iterreduceterse]: http://notriddle.com/rustdoc-html-demo-5/associated-types/std/index.html?search=iterator%3CT%3E%2C%20fnmut%20-%3E%20T&filter-crate=std # Motivation My primary motivation for working on search.js at all is to make it easier to use highly generic APIs, like the Iterator API. The type signature describes these functions pretty well, while the names are almost arbitrary. Before this PR, type bindings were not consistently included in search-index.js at all (you couldn't find Iterator::next by typing in its function signature) and you couldn't explicitly search for them. This PR fixes both of these problems. # Guide-level explanation *Excerpt from [the Rustdoc book](http://notriddle.com/rustdoc-html-demo-5/associated-types/rustdoc/read-documentation/search.html), included in this PR.* > 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<Item=u32> -> usize` > * `Iterator<u32> -> usize` (you can leave out the `Item=` part) > * `Iterator -> usize` (you can leave out iterator's generic entirely) > * `T -> usize` (you can match with a generic parameter) > > Each of the above queries is progressively looser, except the last one would not match `dyn Iterator`, since that's not a type parameter. # Reference-level explanation Inside the angle brackets, you can choose whether to write a name before the parameter and the equal sign. This syntax is called [`GenericArgsBinding`](https://doc.rust-lang.org/reference/paths.html#paths-in-expressions) in the Rust Reference, and it allows you to constrain a trait's associated type. As a convenience, you don't actually have to put the name in (Rust requires it, but Rustdoc Search doesn't). This works about the same way unboxing already works in Search: the terse `Iterator<u32>` is a match for `Iterator<Item=u32>`, but the opposite is not true, just like `u32` is a match for `Iterator<u32>`. When converting a trait method for the search index, the trait is substituted for `Self`, and all associated types are bound to generics. This way, if you have the following trait definition: ```rust pub trait MyTrait { type Output; fn method(self) -> Self::Output; } ``` The following queries will match its method: * `MyTrait<Output=T> -> T` * `MyTrait<T> -> T` * `MyTrait -> T` But these queries will not match it: * <i>`MyTrait<Output=u32> -> u32`</i> * <i>`MyTrait<Output> -> Output`</i> * <i>`MyTrait -> MyTrait::Output`</i> # Drawbacks It's a little bit bigger: ```console $ du before/search-index1.74.0.js after/search-index1.74.0.js 4020 before/search-index1.74.0.js 4068 after/search-index1.74.0.js ``` # Rationale and alternatives I don't want to just not do this. On it's own, it's not terribly useful, but in addition to searching by normal traits, this is also intended as a desugaring target for closures. That's why it needs to actually distinguish the two: it allows the future desugaring to distinguish function output and input. The other alternative would be to not allow users to leave out the name, so `iterator<u32>` doesn't work. That would be unfortunate, because mixing up which ones have out params and which ones are plain generics is an easy enough mistake that the Rust compiler itself helps people out with it. # Prior art * <http://neilmitchell.blogspot.com/2020/06/hoogle-searching-overview.html> The current Rustdoc algorithm, both before this PR and after it, has a fairly expensive matching algorithm over a fairly simple file format. Luckily, we aren't trying to scale to all of crates.io, so it's usable, but it's not great when I throw it at docs.servo.org # Unresolved questions Okay, but *how do we want to handle closures?* I know the system will desugar `FnOnce(T) -> U` into `trait:FnOnce<Output=U, primitive:tuple<T>>`, but what if I don't know what trait I'm looking for? This PR can merge with nothing, but it'd be nice to have a plan. Specifically, how should the special form used to handle all varieties of basic callable: primitive:fn (function pointers), and trait:Fn, trait:FnOnce, and trait:FnMut should all be searchable using a single syntax, because I'm always forgetting which one is used in the function I'm looking for. The essential question is how closely we want to copy Rust's own syntax. The tersest way to expression Option::map might be: Option<T>, (T -> U) -> Option<U> That's the approach I would prefer, but nobody's going to attempt it without being told, so maybe this would be better? Option<T>, (fn(T) -> U) -> Option<U> It does require double parens, but at least it's mostly unambiguous. Unfortunately, it looks like the syntax you'd use for function pointers, implying that if you specifically wanted to limit your search to function pointers, you'd need to use `primitive:fn(T) -> U`. Then again, searching is normally case-insensitive, so you'd want that anyway to disambiguate from `trait:Fn(T) -> U`. # Future possibilities ## This thing really needs a ranking algorithm That is, this PR increases the number of matches for some type-based queries. They're usually pretty good matches, but there's still more of them, and it's evident that if you have two functions, `foo(MyTrait<u8>)` and `bar(MyTrait<Item=u8>)`, if the user typed `MyTrait<u8>` then `foo` should show up first. A design choice that these PRs have followed is that adding more stuff to the search query always reduces the number of functions that get matched. The advantage of doing it that way is that you can rank them by just counting how many atoms are in the function's signature (lowest score goes on top). Since it's impossible for a matching function to have fewer atoms than the search query, if there's a function with exactly the same set of atoms in it, then that'll be on top. More complicated ranking algos tend to penalize long documents anyway, if the [distance metrics](https://www.benfrederickson.com/distance-metrics/?utm_source=flipboard&utm_content=other) I found through [Flipboard](https://flipboard.com/`@arnie0426/building-recommender-systems-nvue3iqtgrn10t45)` (and postgresql's `ts_rank_cd`) are anything to go by. Real-world data sets tend to have weird outliers, like they have God Functions with zillions of arguments of all sorts of types, and Rustdoc ought to put such a function at the bottom. The other natural choice would be interleaving with `unifyFunctionTypes` to count the number of unboxings and reorderings. This would compute a distance function, and would do a fine job of ranking the results, as [described here](https://ndmitchell.com/downloads/slides-hoogle_finding_functions_from_types-16_may_2011.pdf) by the Hoogle dev, but is more complicated than it sounds. The current algorithm returns when it finds a result that *exists at all*, but a distance function should find an *optimal solution* to find the smallest sequence of edits. ## This could also use a benchmark suite and some optimization This approach also lends itself to layering a bloom filter in front of the backtracking unification engine. * At load time, hash the typeNameIdMap ID for each atom and set the matching entry in a fixed-size byte array for each function to 1. Call it `fnType.bloomFilter` * At search time, do the same for the atoms in the query (excluding special forms like `[]` that can match more than one thing). Call it `parsedQuery.bloomFilter` * For each function, `if (fnType.bloomFilter | (~parsedQuery.bloomFilter) !== ~0) { return false; }` There's also room to optimize the unification engine itself, by using stacks and persistent data structures instead of copying arrays around, or by using hashing instead of linear scans (the current algorithm was rewritten from one that tried to do that, but was too much to fit in my head and had a bunch of bugs). The advantage of Just Backtracking Better over the bloom filter is that it doesn't require the engine to retain any special algebraic properties. But, first, we need a set of benchmarks to be able to judge if such a thing will actually help. ## Referring to associated types by path *I don't want to implement this one, but if I did, this is how I'd do it.* In Rust, this is represented by a structure called a qualified path, or QPath. They look like this: <Self as Iterator>::Item <F as FnOnce>::Output They can also, if it's unambiguous, use a plain path and just let the system figure it out: Self::Item F::Output In Rustdoc Type-Driven Search, we don't want to force people to be unambiguous. Instead, we should try *all reasonable interpretations*, return results whenever any of them match, and let users make their query more specific if too many results are matches. To enable associated type path searches in Rustdoc, we need to: 1. When lowering a trait method to a search-index.js function signature, Self should be explicitly represented as a generic argument. It should always be assigned `-1`, so that if the user uses `Self` in their search query, we can ensure it always matches the real Self and not something else. Any functions that don't *have* a Self should drop a `0` into the first position of the where clause, to express that there isn't one and reserve the `-1` position. * Reminder: generics are negative, concrete types are positive, and zero is a reserved sentinel. * Right now, `Iterator::next` is lowered as if it were `fn next<T>(self: Iterator<Item=T>) -> Option<T>`. It should become `fn next<Self, T>(self: Self) -> Option<T> where Self: Iterator<Item=T>` instead. 3. Add another backtracking edge to the unification engine, so that when the user writes something like `some::thing`, the interpretation where `some` is a module and `thing` is a standalone item becomes one possible match candidate, while the interpretation where `some` is a trait and `thing` is an associated type is a separate match candidate. The backtracking engine is basically powerful enough to do this already, since unboxing generic type parameters into their traits already requires the ability to do this kind of thing. * When interpreting `some::thing` where `some` is a trait and `thing` is an associated type, it should be treated equivalently to `<self as some>::thing`. If you want to bind it to some generic parameter other than `Self`, you need to explicitly say so. * If no trait called `some` actually exists, treat it as a generic type parameter instead. Track every trait mentioned in the current working function signature, and add a match candidate for each one. * A user that explicitly wants the trait-associated-type interpretation could write a qpath (like `<self as trait>::type`), and a user that explicitly wants the module-item interpretation should use an item type filter (like `struct:module::type`). 4. To actually do the matching, maintain a `Map<(QueryGenericParamId, TraitId), FnGenericParamId>` alongside the existing `Map<QueryGenericParamId, FnGenericParamId>` that is already used to handle plain generic parameters. This works, because, when a trait function signature is lowered to search-index.js, the `rustdoc` backend always generates an FnGenericParamId for every trait associated type it sees mentioned in the function's signature. 5. Parse QPaths. Specifically, * QueryElem adds three new fields. `isQPath` is a boolean flag, and `traitNameId` contains an entry for `typeNameIdMap` corresponding to the trait part of a qpath, and `parentId` may contain either a concrete type ID or a negative number referring to a generic type parameter. The actual `id` of the query elem will always be a negative number, because this is essentially a funny way to add a generic type constraint. * If it's a QPath, then both of those IDs get filled in with the respective parts of the map. The unification engine will check the where clause to ensure the trait actually applies to the generic parameter in question, will check the type parameter constraint, and will add a mapping to `mgens` recording this as a solution. * If it's just a regular path, then `isQPath` is false, and the parser will fill in both `traitNameId` and `parentId` based on the same path. The unification engine, seeing isQPath is false and that these IDs were filled in, will try all three solutions: the path might be part of a concrete type name, or it might be referring to a trait, or it might be referring to a generic type parameter. ### Why not implement QPath searches? I'm not sure if anybody really wants to write such complicated queries. You can do a pretty good job of describing the generic functions in the standard library without resorting to FQPs. These two queries, for example, would both match the Iterator::map function if we added support for higher order function queries and a rule that allows a type to match its *notable traits*. // I like this version, because it's identical to how `Option::map` would be written. // There's a reason why Iterator::map and Option::map have the same name. Iterator<T>, (T -> U) -> Iterator<U> // This version explicitly uses the type parameter constraints. Iterator<Item=T>, (T -> U) -> Iterator<Item=U> If I try to write this one using FQP, however, the results seem worse: // This one is less expressive than the versions that don't use associated type paths. // It matches `Iterator::filter`, while the above two example queries don't. Iterator, (Iterator::Item -> Iterator::Item) -> Iterator // This doesn't work, because the return type of `Iterator::map` is not a generic // parameter with an `Iterator` trait bound. It's a concrete type that // implements `Iterator`. Return-Position-Impl-Trait is the same way. // // There's a difference between something like `map`, whose return value // implements Iterator, and something like `collect`, where the caller // gets to decide what the concrete type is going to be. //Self, (Self::Item -> I::Item) -> I where Self: Iterator, I: Iterator // This works, but it seems subjectively ugly, complex, and counterintuitive to me. Self, (<Self as Iterator>::Item -> T) -> Iterator<Item=T>
This commit is contained in:
commit
e62f7be638
@ -72,6 +72,7 @@ the standard library and functions that are included in the results list:
|
||||
| [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
|
||||
| [`any -> !`][] | `panic::panic_any` |
|
||||
| [`vec::intoiter<T> -> [T]`][iterasslice] | `IntoIter::as_slice` and `IntoIter::next_chunk` |
|
||||
| [`iterator<T>, fnmut -> T`][iterreduce] | `Iterator::reduce` and `Iterator::find` |
|
||||
|
||||
[`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
|
||||
@ -81,6 +82,7 @@ the standard library and functions that are included in the results list:
|
||||
[`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
|
||||
[iterreduce]: ../../std/index.html?search=iterator<T>%2C%20fnmut%20->%20T&filter-crate=std
|
||||
|
||||
### How type-based search works
|
||||
|
||||
@ -95,7 +97,9 @@ 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.
|
||||
query must be present in the function for it to match. The `self` parameter is
|
||||
treated the same as any other parameter, and `Self` is resolved to the
|
||||
underlying type's name.
|
||||
|
||||
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
|
||||
@ -103,8 +107,37 @@ 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`
|
||||
* `Iterator<Item=u32> -> usize`
|
||||
* `Iterator<u32> -> usize` (you can leave out the `Item=` part)
|
||||
* `Iterator -> usize` (you can leave out iterator's generic entirely)
|
||||
* `T -> usize` (you can match with a generic parameter)
|
||||
|
||||
Each of the above queries is progressively looser, except the last one
|
||||
would not match `dyn Iterator`, since that's not a type parameter.
|
||||
|
||||
If a bound has multiple associated types, specifying the name allows you to
|
||||
pick which one gets matched. If no name is specified, then the query will
|
||||
match of any of them. For example,
|
||||
|
||||
```rust
|
||||
pub trait MyTrait {
|
||||
type First;
|
||||
type Second;
|
||||
}
|
||||
|
||||
/// This function can be found using the following search queries:
|
||||
///
|
||||
/// MyTrait<First=u8, Second=u32> -> bool
|
||||
/// MyTrait<u32, First=u8> -> bool
|
||||
/// MyTrait<Second=u32> -> bool
|
||||
/// MyTrait<u32, u8> -> bool
|
||||
///
|
||||
/// The following queries, however, will *not* match it:
|
||||
///
|
||||
/// MyTrait<First=u32> -> bool
|
||||
/// MyTrait<u32, u32> -> bool
|
||||
pub fn my_fn(x: impl MyTrait<First=u8, Second=u32>) -> bool { true }
|
||||
```
|
||||
|
||||
Generics and function parameters are order-agnostic, but sensitive to nesting
|
||||
and number of matches. For example, a function with the signature
|
||||
@ -134,6 +167,10 @@ Most of these limitations should be addressed in future version of Rustdoc.
|
||||
with that bound, it'll match, but `option<T> -> T where T: Default`
|
||||
cannot be precisely searched for (use `option<Default> -> Default`).
|
||||
|
||||
* Supertraits, type aliases, and Deref are all ignored. Search mostly
|
||||
operates on type signatures *as written*, and not as they are
|
||||
represented within the compiler.
|
||||
|
||||
* 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
|
||||
@ -183,7 +220,8 @@ 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)
|
||||
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
|
||||
generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
|
||||
CLOSE-ANGLE-BRACKET
|
||||
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
|
||||
|
||||
@ -230,6 +268,7 @@ DOUBLE-COLON = "::"
|
||||
QUOTE = %x22
|
||||
COMMA = ","
|
||||
RETURN-ARROW = "->"
|
||||
EQUAL = "="
|
||||
|
||||
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
|
||||
DIGIT = %x30-39
|
||||
|
@ -1651,6 +1651,13 @@ pub(crate) fn is_self_type(&self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_args(&self) -> Option<&GenericArgs> {
|
||||
match self {
|
||||
Type::Path { path, .. } => path.generic_args(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
|
||||
match self {
|
||||
Type::Path { path, .. } => path.generics(),
|
||||
@ -2191,6 +2198,10 @@ pub(crate) fn is_assoc_ty(&self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_args(&self) -> Option<&GenericArgs> {
|
||||
self.segments.last().map(|seg| &seg.args)
|
||||
}
|
||||
|
||||
pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
|
||||
self.segments.last().and_then(|seg| {
|
||||
if let GenericArgs::AngleBracketed { ref args, .. } = seg.args {
|
||||
@ -2232,6 +2243,39 @@ pub(crate) fn is_empty(&self) -> bool {
|
||||
GenericArgs::Parenthesized { inputs, output } => inputs.is_empty() && output.is_none(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn bindings<'a>(&'a self) -> Box<dyn Iterator<Item = TypeBinding> + 'a> {
|
||||
match self {
|
||||
&GenericArgs::AngleBracketed { ref bindings, .. } => Box::new(bindings.iter().cloned()),
|
||||
&GenericArgs::Parenthesized { ref output, .. } => Box::new(
|
||||
output
|
||||
.as_ref()
|
||||
.map(|ty| TypeBinding {
|
||||
assoc: PathSegment {
|
||||
name: sym::Output,
|
||||
args: GenericArgs::AngleBracketed {
|
||||
args: Vec::new().into_boxed_slice(),
|
||||
bindings: ThinVec::new(),
|
||||
},
|
||||
},
|
||||
kind: TypeBindingKind::Equality { term: Term::Type((**ty).clone()) },
|
||||
})
|
||||
.into_iter(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a GenericArgs {
|
||||
type IntoIter = Box<dyn Iterator<Item = GenericArg> + 'a>;
|
||||
type Item = GenericArg;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
&GenericArgs::AngleBracketed { ref args, .. } => Box::new(args.iter().cloned()),
|
||||
&GenericArgs::Parenthesized { ref inputs, .. } => {
|
||||
Box::new(inputs.iter().cloned().map(GenericArg::Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
|
@ -369,6 +369,7 @@ fn is_from_private_dep(tcx: TyCtxt<'_>, cache: &Cache, def_id: DefId) -> bool {
|
||||
&item,
|
||||
self.tcx,
|
||||
clean_impl_generics(self.cache.parent_stack.last()).as_ref(),
|
||||
parent,
|
||||
self.cache,
|
||||
),
|
||||
aliases: item.attrs.get_doc_aliases(),
|
||||
|
@ -49,6 +49,8 @@ pub(crate) enum ItemType {
|
||||
ProcAttribute = 23,
|
||||
ProcDerive = 24,
|
||||
TraitAlias = 25,
|
||||
// This number is reserved for use in JavaScript
|
||||
// Generic = 26,
|
||||
}
|
||||
|
||||
impl Serialize for ItemType {
|
||||
|
@ -113,6 +113,7 @@ pub(crate) struct IndexItem {
|
||||
pub(crate) struct RenderType {
|
||||
id: Option<RenderTypeId>,
|
||||
generics: Option<Vec<RenderType>>,
|
||||
bindings: Option<Vec<(RenderTypeId, Vec<RenderType>)>>,
|
||||
}
|
||||
|
||||
impl Serialize for RenderType {
|
||||
@ -129,10 +130,15 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
Some(RenderTypeId::Index(idx)) => *idx,
|
||||
_ => panic!("must convert render types to indexes before serializing"),
|
||||
};
|
||||
if let Some(generics) = &self.generics {
|
||||
if self.generics.is_some() || self.bindings.is_some() {
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
seq.serialize_element(&id)?;
|
||||
seq.serialize_element(generics)?;
|
||||
seq.serialize_element(self.generics.as_ref().map(Vec::as_slice).unwrap_or_default())?;
|
||||
if self.bindings.is_some() {
|
||||
seq.serialize_element(
|
||||
self.bindings.as_ref().map(Vec::as_slice).unwrap_or_default(),
|
||||
)?;
|
||||
}
|
||||
seq.end()
|
||||
} else {
|
||||
id.serialize(serializer)
|
||||
@ -140,13 +146,31 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum RenderTypeId {
|
||||
DefId(DefId),
|
||||
Primitive(clean::PrimitiveType),
|
||||
AssociatedType(Symbol),
|
||||
Index(isize),
|
||||
}
|
||||
|
||||
impl Serialize for RenderTypeId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let id = match &self {
|
||||
// 0 is a sentinel, everything else is one-indexed
|
||||
// concrete type
|
||||
RenderTypeId::Index(idx) if *idx >= 0 => idx + 1,
|
||||
// generic type parameter
|
||||
RenderTypeId::Index(idx) => *idx,
|
||||
_ => panic!("must convert render types to indexes before serializing"),
|
||||
};
|
||||
id.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Full type of functions/methods in the search index.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IndexItemFunctionType {
|
||||
@ -171,17 +195,22 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
} else {
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
match &self.inputs[..] {
|
||||
[one] if one.generics.is_none() => seq.serialize_element(one)?,
|
||||
[one] if one.generics.is_none() && one.bindings.is_none() => {
|
||||
seq.serialize_element(one)?
|
||||
}
|
||||
_ => seq.serialize_element(&self.inputs)?,
|
||||
}
|
||||
match &self.output[..] {
|
||||
[] if self.where_clause.is_empty() => {}
|
||||
[one] if one.generics.is_none() => seq.serialize_element(one)?,
|
||||
[one] if one.generics.is_none() && one.bindings.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()
|
||||
&& one.bindings.is_none()
|
||||
{
|
||||
seq.serialize_element(one)?;
|
||||
} else {
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
|
||||
use thin_vec::ThinVec;
|
||||
|
||||
use crate::clean;
|
||||
use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
|
||||
@ -22,6 +24,7 @@ pub(crate) fn build_index<'tcx>(
|
||||
) -> String {
|
||||
let mut itemid_to_pathid = FxHashMap::default();
|
||||
let mut primitives = FxHashMap::default();
|
||||
let mut associated_types = FxHashMap::default();
|
||||
let mut crate_paths = vec![];
|
||||
|
||||
// Attach all orphan items to the type's definition if the type
|
||||
@ -38,7 +41,13 @@ pub(crate) fn build_index<'tcx>(
|
||||
parent: Some(parent),
|
||||
parent_idx: None,
|
||||
impl_id,
|
||||
search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache),
|
||||
search_type: get_function_type_for_search(
|
||||
item,
|
||||
tcx,
|
||||
impl_generics.as_ref(),
|
||||
Some(parent),
|
||||
cache,
|
||||
),
|
||||
aliases: item.attrs.get_doc_aliases(),
|
||||
deprecation: item.deprecation(tcx),
|
||||
});
|
||||
@ -76,31 +85,81 @@ pub(crate) fn build_index<'tcx>(
|
||||
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, isize>,
|
||||
itemid: F,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
item_type: ItemType,
|
||||
path: &[Symbol],
|
||||
) {
|
||||
) -> RenderTypeId {
|
||||
match map.entry(itemid) {
|
||||
Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())),
|
||||
Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
|
||||
Entry::Vacant(entry) => {
|
||||
let pathid = *lastpathid;
|
||||
entry.insert(pathid);
|
||||
*lastpathid += 1;
|
||||
crate_paths.push((item_type, path.to_vec()));
|
||||
ty.id = Some(RenderTypeId::Index(pathid));
|
||||
RenderTypeId::Index(pathid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_render_type_id(
|
||||
id: RenderTypeId,
|
||||
cache: &mut Cache,
|
||||
itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
|
||||
primitives: &mut FxHashMap<Symbol, isize>,
|
||||
associated_types: &mut FxHashMap<Symbol, isize>,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
) -> Option<RenderTypeId> {
|
||||
let Cache { ref paths, ref external_paths, .. } = *cache;
|
||||
match id {
|
||||
RenderTypeId::DefId(defid) => {
|
||||
if let Some(&(ref fqp, item_type)) =
|
||||
paths.get(&defid).or_else(|| external_paths.get(&defid))
|
||||
{
|
||||
Some(insert_into_map(
|
||||
itemid_to_pathid,
|
||||
ItemId::DefId(defid),
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
item_type,
|
||||
fqp,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
RenderTypeId::Primitive(primitive) => {
|
||||
let sym = primitive.as_sym();
|
||||
Some(insert_into_map(
|
||||
primitives,
|
||||
sym,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
ItemType::Primitive,
|
||||
&[sym],
|
||||
))
|
||||
}
|
||||
RenderTypeId::Index(_) => Some(id),
|
||||
RenderTypeId::AssociatedType(sym) => Some(insert_into_map(
|
||||
associated_types,
|
||||
sym,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
ItemType::AssocType,
|
||||
&[sym],
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_render_type(
|
||||
ty: &mut RenderType,
|
||||
cache: &mut Cache,
|
||||
itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
|
||||
primitives: &mut FxHashMap<Symbol, isize>,
|
||||
associated_types: &mut FxHashMap<Symbol, isize>,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
) {
|
||||
@ -111,48 +170,54 @@ fn convert_render_type(
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
}
|
||||
}
|
||||
let Cache { ref paths, ref external_paths, .. } = *cache;
|
||||
if let Some(bindings) = &mut ty.bindings {
|
||||
bindings.retain_mut(|(associated_type, constraints)| {
|
||||
let converted_associated_type = convert_render_type_id(
|
||||
*associated_type,
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
let Some(converted_associated_type) = converted_associated_type else {
|
||||
return false;
|
||||
};
|
||||
*associated_type = converted_associated_type;
|
||||
for constraint in constraints {
|
||||
convert_render_type(
|
||||
constraint,
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
let Some(id) = ty.id.clone() else {
|
||||
assert!(ty.generics.is_some());
|
||||
return;
|
||||
};
|
||||
match id {
|
||||
RenderTypeId::DefId(defid) => {
|
||||
if let Some(&(ref fqp, item_type)) =
|
||||
paths.get(&defid).or_else(|| external_paths.get(&defid))
|
||||
{
|
||||
insert_into_map(
|
||||
ty,
|
||||
itemid_to_pathid,
|
||||
ItemId::DefId(defid),
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
item_type,
|
||||
fqp,
|
||||
);
|
||||
} else {
|
||||
ty.id = None;
|
||||
}
|
||||
}
|
||||
RenderTypeId::Primitive(primitive) => {
|
||||
let sym = primitive.as_sym();
|
||||
insert_into_map(
|
||||
ty,
|
||||
primitives,
|
||||
sym,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
ItemType::Primitive,
|
||||
&[sym],
|
||||
);
|
||||
}
|
||||
RenderTypeId::Index(_) => {}
|
||||
}
|
||||
ty.id = convert_render_type_id(
|
||||
id,
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
}
|
||||
if let Some(search_type) = &mut item.search_type {
|
||||
for item in &mut search_type.inputs {
|
||||
@ -161,6 +226,7 @@ fn convert_render_type(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -171,6 +237,7 @@ fn convert_render_type(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -182,6 +249,7 @@ fn convert_render_type(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -442,12 +510,39 @@ pub(crate) fn get_function_type_for_search<'tcx>(
|
||||
item: &clean::Item,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
impl_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
parent: Option<DefId>,
|
||||
cache: &Cache,
|
||||
) -> Option<IndexItemFunctionType> {
|
||||
let mut trait_info = None;
|
||||
let impl_or_trait_generics = impl_generics.or_else(|| {
|
||||
if let Some(def_id) = parent
|
||||
&& let Some(trait_) = cache.traits.get(&def_id)
|
||||
&& let Some((path, _)) =
|
||||
cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id))
|
||||
{
|
||||
let path = clean::Path {
|
||||
res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id),
|
||||
segments: path
|
||||
.iter()
|
||||
.map(|name| clean::PathSegment {
|
||||
name: *name,
|
||||
args: clean::GenericArgs::AngleBracketed {
|
||||
args: Vec::new().into_boxed_slice(),
|
||||
bindings: ThinVec::new(),
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
trait_info = Some((clean::Type::Path { path }, trait_.generics.clone()));
|
||||
Some(trait_info.as_ref().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
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),
|
||||
clean::FunctionItem(ref f) | clean::MethodItem(ref f, _) | clean::TyMethodItem(ref f) => {
|
||||
get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
@ -457,14 +552,23 @@ pub(crate) fn get_function_type_for_search<'tcx>(
|
||||
Some(IndexItemFunctionType { inputs, output, where_clause })
|
||||
}
|
||||
|
||||
fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType {
|
||||
fn get_index_type(
|
||||
clean_type: &clean::Type,
|
||||
generics: Vec<RenderType>,
|
||||
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
|
||||
) -> RenderType {
|
||||
RenderType {
|
||||
id: get_index_type_id(clean_type),
|
||||
id: get_index_type_id(clean_type, rgen),
|
||||
generics: if generics.is_empty() { None } else { Some(generics) },
|
||||
bindings: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
|
||||
fn get_index_type_id(
|
||||
clean_type: &clean::Type,
|
||||
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
|
||||
) -> Option<RenderTypeId> {
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
match *clean_type {
|
||||
clean::Type::Path { ref path, .. } => Some(RenderTypeId::DefId(path.def_id())),
|
||||
clean::DynTrait(ref bounds, _) => {
|
||||
@ -472,18 +576,27 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
|
||||
}
|
||||
clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
|
||||
clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
|
||||
get_index_type_id(type_)
|
||||
get_index_type_id(type_, rgen)
|
||||
}
|
||||
// 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)),
|
||||
clean::QPath(ref data) => {
|
||||
if data.self_type.is_self_type()
|
||||
&& let Some(clean::Path { res: Res::Def(DefKind::Trait, trait_), .. }) = data.trait_
|
||||
{
|
||||
let idx = -isize::try_from(rgen.len() + 1).unwrap();
|
||||
let (idx, _) = rgen
|
||||
.entry(SimplifiedParam::AssociatedType(trait_, data.assoc.name))
|
||||
.or_insert_with(|| (idx, Vec::new()));
|
||||
Some(RenderTypeId::Index(*idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// Not supported yet
|
||||
clean::BareFunction(_)
|
||||
| clean::Generic(_)
|
||||
| clean::ImplTrait(_)
|
||||
| clean::QPath { .. }
|
||||
| clean::Infer => None,
|
||||
clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,6 +606,9 @@ enum SimplifiedParam {
|
||||
Symbol(Symbol),
|
||||
// every argument-position impl trait is its own type parameter
|
||||
Anonymous(isize),
|
||||
// in a trait definition, the associated types are all bound to
|
||||
// their own type parameter
|
||||
AssociatedType(DefId, Symbol),
|
||||
}
|
||||
|
||||
/// The point of this function is to lower generics and types into the simplified form that the
|
||||
@ -523,10 +639,17 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
}
|
||||
|
||||
// First, check if it's "Self".
|
||||
let mut is_self = false;
|
||||
let mut arg = if let Some(self_) = self_ {
|
||||
match &*arg {
|
||||
Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_,
|
||||
type_ if type_.is_self_type() => self_,
|
||||
Type::BorrowedRef { type_, .. } if type_.is_self_type() => {
|
||||
is_self = true;
|
||||
self_
|
||||
}
|
||||
type_ if type_.is_self_type() => {
|
||||
is_self = true;
|
||||
self_
|
||||
}
|
||||
arg => arg,
|
||||
}
|
||||
} else {
|
||||
@ -585,11 +708,19 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
}
|
||||
}
|
||||
if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) {
|
||||
res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None });
|
||||
res.push(RenderType {
|
||||
id: Some(RenderTypeId::Index(*idx)),
|
||||
generics: None,
|
||||
bindings: 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 });
|
||||
res.push(RenderType {
|
||||
id: Some(RenderTypeId::Index(idx)),
|
||||
generics: None,
|
||||
bindings: None,
|
||||
});
|
||||
}
|
||||
} else if let Type::ImplTrait(ref bounds) = *arg {
|
||||
let mut type_bounds = Vec::new();
|
||||
@ -611,12 +742,16 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
}
|
||||
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) });
|
||||
res.push(RenderType { id: None, generics: Some(type_bounds), bindings: None });
|
||||
} 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 });
|
||||
res.push(RenderType {
|
||||
id: Some(RenderTypeId::Index(idx)),
|
||||
generics: None,
|
||||
bindings: None,
|
||||
});
|
||||
}
|
||||
} else if let Type::Slice(ref ty) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
@ -631,7 +766,7 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
res.push(get_index_type(arg, ty_generics, rgen));
|
||||
} else if let Type::Array(ref ty, _) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
simplify_fn_type(
|
||||
@ -645,7 +780,7 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
res.push(get_index_type(arg, ty_generics, rgen));
|
||||
} else if let Type::Tuple(ref tys) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
for ty in tys {
|
||||
@ -661,7 +796,7 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
cache,
|
||||
);
|
||||
}
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
res.push(get_index_type(arg, ty_generics, rgen));
|
||||
} 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.
|
||||
@ -669,12 +804,16 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
// So in here, we can add it directly and look for its own type parameters (so for `Option`,
|
||||
// we will look for them but not for `T`).
|
||||
let mut ty_generics = Vec::new();
|
||||
if let Some(arg_generics) = arg.generics() {
|
||||
for gen in arg_generics.iter() {
|
||||
let mut ty_bindings = Vec::new();
|
||||
if let Some(arg_generics) = arg.generic_args() {
|
||||
for ty in arg_generics.into_iter().filter_map(|gen| match gen {
|
||||
clean::GenericArg::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
}) {
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
gen,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
@ -683,17 +822,180 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
cache,
|
||||
);
|
||||
}
|
||||
for binding in arg_generics.bindings() {
|
||||
simplify_fn_binding(
|
||||
self_,
|
||||
generics,
|
||||
&binding,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_bindings,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
let id = get_index_type_id(&arg);
|
||||
// Every trait associated type on self gets assigned to a type parameter index
|
||||
// this same one is used later for any appearances of these types
|
||||
//
|
||||
// for example, Iterator::next is:
|
||||
//
|
||||
// trait Iterator {
|
||||
// fn next(&mut self) -> Option<Self::Item>
|
||||
// }
|
||||
//
|
||||
// Self is technically just Iterator, but we want to pretend it's more like this:
|
||||
//
|
||||
// fn next<T>(self: Iterator<Item=T>) -> Option<T>
|
||||
if is_self
|
||||
&& let Type::Path { path } = arg
|
||||
&& let def_id = path.def_id()
|
||||
&& let Some(trait_) = cache.traits.get(&def_id)
|
||||
&& trait_.items.iter().any(|at| at.is_ty_associated_type())
|
||||
{
|
||||
for assoc_ty in &trait_.items {
|
||||
if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &*assoc_ty.kind
|
||||
&& let Some(name) = assoc_ty.name
|
||||
{
|
||||
let idx = -isize::try_from(rgen.len() + 1).unwrap();
|
||||
let (idx, stored_bounds) = rgen
|
||||
.entry(SimplifiedParam::AssociatedType(def_id, name))
|
||||
.or_insert_with(|| (idx, Vec::new()));
|
||||
let idx = *idx;
|
||||
if stored_bounds.is_empty() {
|
||||
// Can't just pass stored_bounds to simplify_fn_type,
|
||||
// because it also accepts rgen as a parameter.
|
||||
// Instead, have it fill in this local, then copy it into the map afterward.
|
||||
let mut type_bounds = Vec::new();
|
||||
for bound in bounds {
|
||||
if let Some(path) = bound.get_trait_path() {
|
||||
let ty = Type::Path { path };
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut type_bounds,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
let stored_bounds = &mut rgen
|
||||
.get_mut(&SimplifiedParam::AssociatedType(def_id, name))
|
||||
.unwrap()
|
||||
.1;
|
||||
if stored_bounds.is_empty() {
|
||||
*stored_bounds = type_bounds;
|
||||
}
|
||||
}
|
||||
ty_bindings.push((
|
||||
RenderTypeId::AssociatedType(name),
|
||||
vec![RenderType {
|
||||
id: Some(RenderTypeId::Index(idx)),
|
||||
generics: None,
|
||||
bindings: None,
|
||||
}],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
let id = get_index_type_id(&arg, rgen);
|
||||
if id.is_some() || !ty_generics.is_empty() {
|
||||
res.push(RenderType {
|
||||
id,
|
||||
bindings: if ty_bindings.is_empty() { None } else { Some(ty_bindings) },
|
||||
generics: if ty_generics.is_empty() { None } else { Some(ty_generics) },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn simplify_fn_binding<'tcx, 'a>(
|
||||
self_: Option<&'a Type>,
|
||||
generics: &Generics,
|
||||
binding: &'a clean::TypeBinding,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
recurse: usize,
|
||||
res: &mut Vec<(RenderTypeId, Vec<RenderType>)>,
|
||||
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
|
||||
is_return: bool,
|
||||
cache: &Cache,
|
||||
) {
|
||||
let mut ty_binding_constraints = Vec::new();
|
||||
let ty_binding_assoc = RenderTypeId::AssociatedType(binding.assoc.name);
|
||||
for gen in &binding.assoc.args {
|
||||
match gen {
|
||||
clean::GenericArg::Type(arg) => simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&arg,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_binding_constraints,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
),
|
||||
clean::GenericArg::Lifetime(_)
|
||||
| clean::GenericArg::Const(_)
|
||||
| clean::GenericArg::Infer => {}
|
||||
}
|
||||
}
|
||||
for binding in binding.assoc.args.bindings() {
|
||||
simplify_fn_binding(
|
||||
self_,
|
||||
generics,
|
||||
&binding,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
res,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
match &binding.kind {
|
||||
clean::TypeBindingKind::Equality { term } => {
|
||||
if let clean::Term::Type(arg) = &term {
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
arg,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_binding_constraints,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
clean::TypeBindingKind::Constraint { bounds } => {
|
||||
for bound in &bounds[..] {
|
||||
if let Some(path) = bound.get_trait_path() {
|
||||
let ty = Type::Path { path };
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_binding_constraints,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res.push((ty_binding_assoc, ty_binding_constraints));
|
||||
}
|
||||
|
||||
/// Return the full list of types when bounds have been resolved.
|
||||
///
|
||||
/// i.e. `fn foo<A: Display, B: Option<A>>(x: u32, y: B)` will return
|
||||
@ -701,13 +1003,15 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
fn get_fn_inputs_and_outputs<'tcx>(
|
||||
func: &Function,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
impl_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
impl_or_trait_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
cache: &Cache,
|
||||
) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) {
|
||||
let decl = &func.decl;
|
||||
|
||||
let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
|
||||
|
||||
let combined_generics;
|
||||
let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_generics {
|
||||
let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_or_trait_generics {
|
||||
match (impl_generics.is_empty(), func.generics.is_empty()) {
|
||||
(true, _) => (Some(impl_self), &func.generics),
|
||||
(_, true) => (Some(impl_self), impl_generics),
|
||||
@ -729,8 +1033,6 @@ fn get_fn_inputs_and_outputs<'tcx>(
|
||||
(None, &func.generics)
|
||||
};
|
||||
|
||||
let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
|
||||
|
||||
let mut arg_types = Vec::new();
|
||||
for arg in decl.inputs.values.iter() {
|
||||
simplify_fn_type(
|
||||
|
@ -14,6 +14,7 @@ function initSearch(searchIndex){}
|
||||
* pathWithoutLast: Array<string>,
|
||||
* pathLast: string,
|
||||
* generics: Array<QueryElement>,
|
||||
* bindings: Map<(string|integer), Array<QueryElement>>,
|
||||
* }}
|
||||
*/
|
||||
let QueryElement;
|
||||
@ -24,6 +25,7 @@ let QueryElement;
|
||||
* totalElems: number,
|
||||
* typeFilter: (null|string),
|
||||
* userQuery: string,
|
||||
* isInBinding: (null|string),
|
||||
* }}
|
||||
*/
|
||||
let ParserState;
|
||||
@ -191,8 +193,9 @@ let FunctionSearchType;
|
||||
/**
|
||||
* @typedef {{
|
||||
* id: (null|number),
|
||||
* ty: (null|number),
|
||||
* ty: number,
|
||||
* generics: Array<FunctionType>,
|
||||
* bindings: Map<integer, Array<FunctionType>>,
|
||||
* }}
|
||||
*/
|
||||
let FunctionType;
|
||||
|
@ -23,27 +23,27 @@ const itemTypes = [
|
||||
"import",
|
||||
"struct",
|
||||
"enum",
|
||||
"fn",
|
||||
"fn", // 5
|
||||
"type",
|
||||
"static",
|
||||
"trait",
|
||||
"impl",
|
||||
"tymethod",
|
||||
"tymethod", // 10
|
||||
"method",
|
||||
"structfield",
|
||||
"variant",
|
||||
"macro",
|
||||
"primitive",
|
||||
"primitive", // 15
|
||||
"associatedtype",
|
||||
"constant",
|
||||
"associatedconstant",
|
||||
"union",
|
||||
"foreigntype",
|
||||
"foreigntype", // 20
|
||||
"keyword",
|
||||
"existential",
|
||||
"attr",
|
||||
"derive",
|
||||
"traitalias",
|
||||
"traitalias", // 25
|
||||
"generic",
|
||||
];
|
||||
|
||||
@ -298,7 +298,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
|
||||
function isEndCharacter(c) {
|
||||
return ",>-]".indexOf(c) !== -1;
|
||||
return "=,>-]".indexOf(c) !== -1;
|
||||
}
|
||||
|
||||
function isStopCharacter(c) {
|
||||
@ -398,7 +398,7 @@ function initSearch(rawSearchIndex) {
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isSeparatorCharacter(c) {
|
||||
return c === ",";
|
||||
return c === "," || c === "=";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -500,6 +500,8 @@ function initSearch(rawSearchIndex) {
|
||||
" does not accept generic parameters",
|
||||
];
|
||||
}
|
||||
const bindingName = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
return {
|
||||
name: "never",
|
||||
id: null,
|
||||
@ -507,7 +509,9 @@ function initSearch(rawSearchIndex) {
|
||||
pathWithoutLast: [],
|
||||
pathLast: "never",
|
||||
generics: [],
|
||||
bindings: new Map(),
|
||||
typeFilter: "primitive",
|
||||
bindingName,
|
||||
};
|
||||
}
|
||||
if (path.startsWith("::")) {
|
||||
@ -542,14 +546,27 @@ function initSearch(rawSearchIndex) {
|
||||
if (isInGenerics) {
|
||||
parserState.genericsElems += 1;
|
||||
}
|
||||
const bindingName = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
const bindings = new Map();
|
||||
return {
|
||||
name: name.trim(),
|
||||
id: null,
|
||||
fullPath: pathSegments,
|
||||
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
|
||||
pathLast: pathSegments[pathSegments.length - 1],
|
||||
generics: generics,
|
||||
generics: generics.filter(gen => {
|
||||
// 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]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
bindings,
|
||||
typeFilter,
|
||||
bindingName,
|
||||
};
|
||||
}
|
||||
|
||||
@ -608,6 +625,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
} else if (
|
||||
c === "[" ||
|
||||
c === "=" ||
|
||||
isStopCharacter(c) ||
|
||||
isSpecialStartCharacter(c) ||
|
||||
isSeparatorCharacter(c)
|
||||
@ -657,6 +675,7 @@ function initSearch(rawSearchIndex) {
|
||||
parserState.pos += 1;
|
||||
getItemsBefore(query, parserState, generics, "]");
|
||||
const typeFilter = parserState.typeFilter;
|
||||
const isInBinding = parserState.isInBinding;
|
||||
if (typeFilter !== null && typeFilter !== "primitive") {
|
||||
throw [
|
||||
"Invalid search type: primitive ",
|
||||
@ -667,10 +686,16 @@ function initSearch(rawSearchIndex) {
|
||||
];
|
||||
}
|
||||
parserState.typeFilter = null;
|
||||
parserState.isInBinding = null;
|
||||
parserState.totalElems += 1;
|
||||
if (isInGenerics) {
|
||||
parserState.genericsElems += 1;
|
||||
}
|
||||
for (const gen of generics) {
|
||||
if (gen.bindingName !== null) {
|
||||
throw ["Type parameter ", "=", " cannot be within slice ", "[]"];
|
||||
}
|
||||
}
|
||||
elems.push({
|
||||
name: "[]",
|
||||
id: null,
|
||||
@ -679,6 +704,8 @@ function initSearch(rawSearchIndex) {
|
||||
pathLast: "[]",
|
||||
generics,
|
||||
typeFilter: "primitive",
|
||||
bindingName: isInBinding,
|
||||
bindings: new Map(),
|
||||
});
|
||||
} else {
|
||||
const isStringElem = parserState.userQuery[start] === "\"";
|
||||
@ -705,15 +732,38 @@ function initSearch(rawSearchIndex) {
|
||||
if (start >= end && generics.length === 0) {
|
||||
return;
|
||||
}
|
||||
elems.push(
|
||||
createQueryElement(
|
||||
query,
|
||||
parserState,
|
||||
parserState.userQuery.slice(start, end),
|
||||
generics,
|
||||
isInGenerics
|
||||
)
|
||||
);
|
||||
if (parserState.userQuery[parserState.pos] === "=") {
|
||||
if (parserState.isInBinding) {
|
||||
throw ["Cannot write ", "=", " twice in a binding"];
|
||||
}
|
||||
if (!isInGenerics) {
|
||||
throw ["Type parameter ", "=", " must be within generics list"];
|
||||
}
|
||||
const name = parserState.userQuery.slice(start, end).trim();
|
||||
if (name === "!") {
|
||||
throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];
|
||||
}
|
||||
if (name.includes("!")) {
|
||||
throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];
|
||||
}
|
||||
if (name.includes("::")) {
|
||||
throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];
|
||||
}
|
||||
if (name.includes(":")) {
|
||||
throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];
|
||||
}
|
||||
parserState.isInBinding = { name, generics };
|
||||
} else {
|
||||
elems.push(
|
||||
createQueryElement(
|
||||
query,
|
||||
parserState,
|
||||
parserState.userQuery.slice(start, end),
|
||||
generics,
|
||||
isInGenerics
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -737,6 +787,8 @@ function initSearch(rawSearchIndex) {
|
||||
// If this is a generic, keep the outer item's type filter around.
|
||||
const oldTypeFilter = parserState.typeFilter;
|
||||
parserState.typeFilter = null;
|
||||
const oldIsInBinding = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
|
||||
let extra = "";
|
||||
if (endChar === ">") {
|
||||
@ -752,6 +804,9 @@ function initSearch(rawSearchIndex) {
|
||||
while (parserState.pos < parserState.length) {
|
||||
const c = parserState.userQuery[parserState.pos];
|
||||
if (c === endChar) {
|
||||
if (parserState.isInBinding) {
|
||||
throw ["Unexpected ", endChar, " after ", "="];
|
||||
}
|
||||
break;
|
||||
} else if (isSeparatorCharacter(c)) {
|
||||
parserState.pos += 1;
|
||||
@ -791,7 +846,9 @@ function initSearch(rawSearchIndex) {
|
||||
throw [
|
||||
"Expected ",
|
||||
",",
|
||||
" or ",
|
||||
", ",
|
||||
"=",
|
||||
", or ",
|
||||
endChar,
|
||||
...extra,
|
||||
", found ",
|
||||
@ -801,6 +858,8 @@ function initSearch(rawSearchIndex) {
|
||||
throw [
|
||||
"Expected ",
|
||||
",",
|
||||
" or ",
|
||||
"=",
|
||||
...extra,
|
||||
", found ",
|
||||
c,
|
||||
@ -828,6 +887,7 @@ function initSearch(rawSearchIndex) {
|
||||
parserState.pos += 1;
|
||||
|
||||
parserState.typeFilter = oldTypeFilter;
|
||||
parserState.isInBinding = oldIsInBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1054,6 +1114,11 @@ function initSearch(rawSearchIndex) {
|
||||
for (const elem2 of elem.generics) {
|
||||
convertTypeFilterOnElem(elem2);
|
||||
}
|
||||
for (const constraints of elem.bindings.values()) {
|
||||
for (const constraint of constraints) {
|
||||
convertTypeFilterOnElem(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
userQuery = userQuery.trim();
|
||||
const parserState = {
|
||||
@ -1063,6 +1128,7 @@ function initSearch(rawSearchIndex) {
|
||||
totalElems: 0,
|
||||
genericsElems: 0,
|
||||
typeFilter: null,
|
||||
isInBinding: null,
|
||||
userQuery: userQuery.toLowerCase(),
|
||||
};
|
||||
let query = newParsedQuery(userQuery);
|
||||
@ -1342,7 +1408,8 @@ function initSearch(rawSearchIndex) {
|
||||
const fl = fnTypesIn.length;
|
||||
|
||||
// One element fast path / base case
|
||||
if (ql === 1 && queryElems[0].generics.length === 0) {
|
||||
if (ql === 1 && queryElems[0].generics.length === 0
|
||||
&& queryElems[0].bindings.size === 0) {
|
||||
const queryElem = queryElems[0];
|
||||
for (const fnType of fnTypesIn) {
|
||||
if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
|
||||
@ -1453,16 +1520,33 @@ function initSearch(rawSearchIndex) {
|
||||
whereClause,
|
||||
mgensScratch,
|
||||
mgensScratch => {
|
||||
if (fnType.generics.length === 0 && queryElem.generics.length === 0) {
|
||||
if (fnType.generics.length === 0 && queryElem.generics.length === 0
|
||||
&& fnType.bindings.size === 0 && queryElem.bindings.size === 0) {
|
||||
return !solutionCb || solutionCb(mgensScratch);
|
||||
}
|
||||
return unifyFunctionTypes(
|
||||
fnType.generics,
|
||||
queryElem.generics,
|
||||
const solution = unifyFunctionTypeCheckBindings(
|
||||
fnType,
|
||||
queryElem,
|
||||
whereClause,
|
||||
mgensScratch,
|
||||
solutionCb
|
||||
mgensScratch
|
||||
);
|
||||
if (!solution) {
|
||||
return false;
|
||||
}
|
||||
const simplifiedGenerics = solution.simplifiedGenerics;
|
||||
for (const simplifiedMgens of solution.mgens) {
|
||||
const passesUnification = unifyFunctionTypes(
|
||||
simplifiedGenerics,
|
||||
queryElem.generics,
|
||||
whereClause,
|
||||
simplifiedMgens,
|
||||
solutionCb
|
||||
);
|
||||
if (passesUnification) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
if (passesUnification) {
|
||||
@ -1491,8 +1575,11 @@ function initSearch(rawSearchIndex) {
|
||||
const generics = fnType.id < 0 ?
|
||||
whereClause[(-fnType.id) - 1] :
|
||||
fnType.generics;
|
||||
const bindings = fnType.bindings ?
|
||||
Array.from(fnType.bindings.values()).flat() :
|
||||
[];
|
||||
const passesUnification = unifyFunctionTypes(
|
||||
fnTypes.toSpliced(i, 1, ...generics),
|
||||
fnTypes.toSpliced(i, 1, ...generics, ...bindings),
|
||||
queryElems,
|
||||
whereClause,
|
||||
mgensScratch,
|
||||
@ -1504,22 +1591,37 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens) {
|
||||
/**
|
||||
* Check if this function is a match candidate.
|
||||
*
|
||||
* This function is all the fast checks that don't require backtracking.
|
||||
* It checks that two items are not named differently, and is load-bearing for that.
|
||||
* It also checks that, if the query has generics, the function type must have generics
|
||||
* or associated type bindings: that's not load-bearing, but it prevents unnecessary
|
||||
* backtracking later.
|
||||
*
|
||||
* @param {FunctionType} fnType
|
||||
* @param {QueryElement} queryElem
|
||||
* @param {[FunctionSearchType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>|null} mgensIn - Map functions generics to query generics.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgensIn) {
|
||||
// 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
|
||||
// mgensIn[fnType.id] = queryElem.id
|
||||
// or, if mgensIn[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 !== null) {
|
||||
if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) {
|
||||
if (mgensIn) {
|
||||
if (mgensIn.has(fnType.id) && mgensIn.get(fnType.id) !== queryElem.id) {
|
||||
return false;
|
||||
}
|
||||
for (const [fid, qid] of mgens.entries()) {
|
||||
for (const [fid, qid] of mgensIn.entries()) {
|
||||
if (fnType.id !== fid && queryElem.id === qid) {
|
||||
return false;
|
||||
}
|
||||
@ -1528,6 +1630,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (queryElem.id === typeNameIdOfArrayOrSlice &&
|
||||
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray)
|
||||
@ -1539,7 +1642,12 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
// If the query elem has generics, and the function doesn't,
|
||||
// it can't match.
|
||||
if (fnType.generics.length === 0 && queryElem.generics.length !== 0) {
|
||||
if ((fnType.generics.length + fnType.bindings.size) === 0 &&
|
||||
queryElem.generics.length !== 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (fnType.bindings.size < queryElem.bindings.size) {
|
||||
return false;
|
||||
}
|
||||
// If the query element is a path (it contains `::`), we need to check if this
|
||||
@ -1568,9 +1676,87 @@ function initSearch(rawSearchIndex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* This function checks the associated type bindings. Any that aren't matched get converted
|
||||
* to generics, and this function returns an array of the function's generics with these
|
||||
* simplified bindings added to them. That is, it takes a path like this:
|
||||
*
|
||||
* Iterator<Item=u32>
|
||||
*
|
||||
* ... if queryElem itself has an `Item=` in it, then this function returns an empty array.
|
||||
* But if queryElem contains no Item=, then this function returns a one-item array with the
|
||||
* ID of u32 in it, and the rest of the matching engine acts as if `Iterator<u32>` were
|
||||
* the type instead.
|
||||
*
|
||||
* @param {FunctionType} fnType
|
||||
* @param {QueryElement} queryElem
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>} mgensIn - Map functions generics to query generics.
|
||||
* Never modified.
|
||||
* @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
|
||||
*/
|
||||
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
|
||||
if (fnType.bindings.size < queryElem.bindings.size) {
|
||||
return false;
|
||||
}
|
||||
let simplifiedGenerics = fnType.generics || [];
|
||||
if (fnType.bindings.size > 0) {
|
||||
let mgensSolutionSet = [mgensIn];
|
||||
for (const [name, constraints] of queryElem.bindings.entries()) {
|
||||
if (mgensSolutionSet.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (!fnType.bindings.has(name)) {
|
||||
return false;
|
||||
}
|
||||
const fnTypeBindings = fnType.bindings.get(name);
|
||||
mgensSolutionSet = mgensSolutionSet.flatMap(mgens => {
|
||||
const newSolutions = [];
|
||||
unifyFunctionTypes(
|
||||
fnTypeBindings,
|
||||
constraints,
|
||||
whereClause,
|
||||
mgens,
|
||||
newMgens => {
|
||||
newSolutions.push(newMgens);
|
||||
// return `false` makes unifyFunctionTypes return the full set of
|
||||
// possible solutions
|
||||
return false;
|
||||
}
|
||||
);
|
||||
return newSolutions;
|
||||
});
|
||||
}
|
||||
if (mgensSolutionSet.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const binds = Array.from(fnType.bindings.entries()).flatMap(entry => {
|
||||
const [name, constraints] = entry;
|
||||
if (queryElem.bindings.has(name)) {
|
||||
return [];
|
||||
} else {
|
||||
return constraints;
|
||||
}
|
||||
});
|
||||
if (simplifiedGenerics.length > 0) {
|
||||
simplifiedGenerics = [...simplifiedGenerics, ...binds];
|
||||
} else {
|
||||
simplifiedGenerics = binds;
|
||||
}
|
||||
return { simplifiedGenerics, mgens: mgensSolutionSet };
|
||||
}
|
||||
return { simplifiedGenerics, mgens: [mgensIn] };
|
||||
}
|
||||
/**
|
||||
* @param {FunctionType} fnType
|
||||
* @param {QueryElement} queryElem
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) {
|
||||
if (fnType.id < 0 && queryElem.id >= 0) {
|
||||
if (!whereClause) {
|
||||
@ -1578,7 +1764,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
// 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 !== null && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
|
||||
if (mgens && 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
|
||||
@ -1586,8 +1772,12 @@ function initSearch(rawSearchIndex) {
|
||||
// `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);
|
||||
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
|
||||
const simplifiedGenerics = [
|
||||
...fnType.generics,
|
||||
...Array.from(fnType.bindings.values()).flat(),
|
||||
];
|
||||
return checkIfInList(simplifiedGenerics, queryElem, whereClause);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1622,15 +1812,17 @@ function initSearch(rawSearchIndex) {
|
||||
* @return {boolean} - Returns true if the type matches, false otherwise.
|
||||
*/
|
||||
function checkType(row, elem, whereClause) {
|
||||
if (elem.id < 0) {
|
||||
return row.id < 0 || checkIfInList(row.generics, elem, whereClause);
|
||||
}
|
||||
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
|
||||
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
|
||||
// special case
|
||||
elem.id !== typeNameIdOfArrayOrSlice
|
||||
) {
|
||||
return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
|
||||
if (row.bindings.size === 0 && elem.bindings.size === 0) {
|
||||
if (elem.id < 0) {
|
||||
return row.id < 0 || checkIfInList(row.generics, elem, whereClause);
|
||||
}
|
||||
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
|
||||
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
|
||||
// special case
|
||||
elem.id !== typeNameIdOfArrayOrSlice
|
||||
) {
|
||||
return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
|
||||
}
|
||||
}
|
||||
return unifyFunctionTypes([row], [elem], whereClause);
|
||||
}
|
||||
@ -1977,7 +2169,7 @@ function initSearch(rawSearchIndex) {
|
||||
elem.id = match;
|
||||
}
|
||||
if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1
|
||||
&& elem.generics.length === 0)
|
||||
&& elem.generics.length === 0 && elem.bindings.size === 0)
|
||||
|| elem.typeFilter === TY_GENERIC) {
|
||||
if (genericSymbols.has(elem.name)) {
|
||||
elem.id = genericSymbols.get(elem.name);
|
||||
@ -2020,6 +2212,23 @@ function initSearch(rawSearchIndex) {
|
||||
for (const elem2 of elem.generics) {
|
||||
convertNameToId(elem2);
|
||||
}
|
||||
elem.bindings = new Map(Array.from(elem.bindings.entries())
|
||||
.map(entry => {
|
||||
const [name, constraints] = entry;
|
||||
if (!typeNameIdMap.has(name)) {
|
||||
parsedQuery.error = [
|
||||
"Type parameter ",
|
||||
name,
|
||||
" does not exist",
|
||||
];
|
||||
}
|
||||
for (const elem2 of constraints) {
|
||||
convertNameToId(elem2);
|
||||
}
|
||||
|
||||
return [typeNameIdMap.get(name), constraints];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const elem of parsedQuery.elems) {
|
||||
@ -2536,16 +2745,39 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
function buildItemSearchType(type, lowercasePaths) {
|
||||
const PATH_INDEX_DATA = 0;
|
||||
const GENERICS_DATA = 1;
|
||||
let pathIndex, generics;
|
||||
const BINDINGS_DATA = 2;
|
||||
let pathIndex, generics, bindings;
|
||||
if (typeof type === "number") {
|
||||
pathIndex = type;
|
||||
generics = [];
|
||||
bindings = new Map();
|
||||
} else {
|
||||
pathIndex = type[PATH_INDEX_DATA];
|
||||
generics = buildItemSearchTypeAll(
|
||||
type[GENERICS_DATA],
|
||||
lowercasePaths
|
||||
);
|
||||
if (type.length > BINDINGS_DATA) {
|
||||
bindings = new Map(type[BINDINGS_DATA].map(binding => {
|
||||
const [assocType, constraints] = binding;
|
||||
// Associated type constructors are represented sloppily in rustdoc's
|
||||
// type search, to make the engine simpler.
|
||||
//
|
||||
// MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
|
||||
// and both are, essentially
|
||||
// MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
|
||||
// It's more like the value of a type binding is naturally an array,
|
||||
// which rustdoc calls "constraints".
|
||||
//
|
||||
// As a result, the key should never have generics on it.
|
||||
return [
|
||||
buildItemSearchType(assocType, lowercasePaths).id,
|
||||
buildItemSearchTypeAll(constraints, lowercasePaths),
|
||||
];
|
||||
}));
|
||||
} else {
|
||||
bindings = new Map();
|
||||
}
|
||||
}
|
||||
if (pathIndex < 0) {
|
||||
// types less than 0 are generic parameters
|
||||
@ -2555,6 +2787,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
ty: TY_GENERIC,
|
||||
path: null,
|
||||
generics,
|
||||
bindings,
|
||||
};
|
||||
}
|
||||
if (pathIndex === 0) {
|
||||
@ -2564,6 +2797,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
ty: null,
|
||||
path: null,
|
||||
generics,
|
||||
bindings,
|
||||
};
|
||||
}
|
||||
const item = lowercasePaths[pathIndex - 1];
|
||||
@ -2572,6 +2806,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
ty: item.ty,
|
||||
path: item.path,
|
||||
generics,
|
||||
bindings,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,31 @@ function checkNeededFields(fullPath, expected, error_text, queryName, position)
|
||||
}
|
||||
|
||||
function valueCheck(fullPath, expected, result, error_text, queryName) {
|
||||
if (Array.isArray(expected)) {
|
||||
if (Array.isArray(expected) && result instanceof Map) {
|
||||
const expected_set = new Set();
|
||||
for (const [key, expected_value] of expected) {
|
||||
expected_set.add(key);
|
||||
checkNeededFields(fullPath, expected_value, error_text, queryName, key);
|
||||
if (result.has(key)) {
|
||||
valueCheck(
|
||||
fullPath + "[" + key + "]",
|
||||
expected_value,
|
||||
result.get(key),
|
||||
error_text,
|
||||
queryName
|
||||
);
|
||||
} else {
|
||||
error_text.push(`${queryName}==> EXPECTED has extra key in map from field ` +
|
||||
`\`${fullPath}\` (key ${key}): \`${JSON.stringify(expected_value)}\``);
|
||||
}
|
||||
}
|
||||
for (const [key, result_value] of result.entries()) {
|
||||
if (!expected_set.has(key)) {
|
||||
error_text.push(`${queryName}==> EXPECTED missing key in map from field ` +
|
||||
`\`${fullPath}\` (key ${key}): \`${JSON.stringify(result_value)}\``);
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(expected)) {
|
||||
let i;
|
||||
for (i = 0; i < expected.length; ++i) {
|
||||
checkNeededFields(fullPath, expected[i], error_text, queryName, i);
|
||||
@ -153,6 +177,9 @@ function valueCheck(fullPath, expected, result, error_text, queryName) {
|
||||
}
|
||||
let result_v = result[key];
|
||||
if (result_v !== null && key === "error") {
|
||||
if (!result_v.forEach) {
|
||||
throw result_v;
|
||||
}
|
||||
result_v.forEach((value, index) => {
|
||||
value = value.split(" ").join(" ");
|
||||
if (index % 2 === 1) {
|
||||
|
@ -80,7 +80,7 @@ set-window-size: (851, 600)
|
||||
|
||||
// Check the size and count in tabs
|
||||
assert-text: ("#search-tabs > button:nth-child(1) > .count", " (25) ")
|
||||
assert-text: ("#search-tabs > button:nth-child(2) > .count", " (5) ")
|
||||
assert-text: ("#search-tabs > button:nth-child(2) > .count", " (6) ")
|
||||
assert-text: ("#search-tabs > button:nth-child(3) > .count", " (0) ")
|
||||
store-property: ("#search-tabs > button:nth-child(1)", {"offsetWidth": buttonWidth})
|
||||
assert-property: ("#search-tabs > button:nth-child(2)", {"offsetWidth": |buttonWidth|})
|
||||
|
29
tests/rustdoc-js-std/iterator-type-signatures.js
Normal file
29
tests/rustdoc-js-std/iterator-type-signatures.js
Normal file
@ -0,0 +1,29 @@
|
||||
// ignore-order
|
||||
|
||||
const FILTER_CRATE = "std";
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'iterator<t> -> option<t>',
|
||||
'others': [
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'max' },
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'min' },
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'last' },
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'iterator<t>, usize -> option<t>',
|
||||
'others': [
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'nth' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// Something should be done so that intoiterator is considered a match
|
||||
// for plain iterator.
|
||||
'query': 'iterator<t>, intoiterator<t> -> ordering',
|
||||
'others': [
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'cmp' },
|
||||
],
|
||||
},
|
||||
];
|
245
tests/rustdoc-js-std/parser-bindings.js
Normal file
245
tests/rustdoc-js-std/parser-bindings.js
Normal file
@ -0,0 +1,245 @@
|
||||
const PARSED = [
|
||||
{
|
||||
query: 'A<B=C>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[
|
||||
{
|
||||
name: "c",
|
||||
fullPath: ["c"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "c",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=C>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=c>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B = C>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "c",
|
||||
fullPath: ["c"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "c",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B = C>',
|
||||
returned: [],
|
||||
userQuery: 'a<b = c>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=!>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "never",
|
||||
fullPath: ["never"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "never",
|
||||
generics: [],
|
||||
typeFilter: 15,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=!>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=!>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=[]>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "[]",
|
||||
fullPath: ["[]"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "[]",
|
||||
generics: [],
|
||||
typeFilter: 15,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=[]>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=[]>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=[!]>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "[]",
|
||||
fullPath: ["[]"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "[]",
|
||||
generics: [
|
||||
{
|
||||
name: "never",
|
||||
fullPath: ["never"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "never",
|
||||
generics: [],
|
||||
typeFilter: 15,
|
||||
},
|
||||
],
|
||||
typeFilter: 15,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=[!]>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=[!]>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=C=>',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: 'A<B=C=>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=c=>',
|
||||
error: "Cannot write `=` twice in a binding",
|
||||
},
|
||||
{
|
||||
query: 'A<B=>',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: 'A<B=>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=>',
|
||||
error: "Unexpected `>` after `=`",
|
||||
},
|
||||
{
|
||||
query: 'B=C',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: 'B=C',
|
||||
returned: [],
|
||||
userQuery: 'b=c',
|
||||
error: "Type parameter `=` must be within generics list",
|
||||
},
|
||||
{
|
||||
query: '[B=C]',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: '[B=C]',
|
||||
returned: [],
|
||||
userQuery: '[b=c]',
|
||||
error: "Type parameter `=` cannot be within slice `[]`",
|
||||
},
|
||||
{
|
||||
query: 'A<B<X>=C>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[
|
||||
{
|
||||
name: "c",
|
||||
fullPath: ["c"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "c",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "x",
|
||||
fullPath: ["x"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "x",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B<X>=C>',
|
||||
returned: [],
|
||||
userQuery: 'a<b<x>=c>',
|
||||
error: null,
|
||||
},
|
||||
];
|
@ -303,7 +303,7 @@ const PARSED = [
|
||||
original: '->a<>b',
|
||||
returned: [],
|
||||
userQuery: '->a<>b',
|
||||
error: 'Expected `,` after `>`, found `b`',
|
||||
error: 'Expected `,` or `=` after `>`, found `b`',
|
||||
},
|
||||
{
|
||||
query: "a<->",
|
||||
|
163
tests/rustdoc-js/assoc-type-backtrack.js
Normal file
163
tests/rustdoc-js/assoc-type-backtrack.js
Normal file
@ -0,0 +1,163 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'mytrait, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<U>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=U>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<T>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=T>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<T> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=T> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<U> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=U> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
// The first two define the base case.
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
// Unboxings of the one-argument case.
|
||||
{
|
||||
'query': 'myfuture<t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
// Invalid unboxing of the one-argument case.
|
||||
// If you unbox one of the myfutures, you need to unbox both of them.
|
||||
{
|
||||
'query': 'myintofuture<fut=t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
// Unboxings of the two-argument case.
|
||||
{
|
||||
'query': 'myintofuture<fut=t>, myintofuture<fut=t> -> t',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture>, myintofuture<fut=myfuture> -> myfuture',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<myfuture>, myintofuture<myfuture> -> myfuture',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myfuture<t>, myfuture<t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
// Invalid unboxings of the two-argument case.
|
||||
// If you unbox one of the myfutures, you need to unbox all of them.
|
||||
{
|
||||
'query': 'myintofuture<fut=t>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> t',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
// different generics don't match up either
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<u>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<output=t> -> myfuture<tt>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
];
|
38
tests/rustdoc-js/assoc-type-backtrack.rs
Normal file
38
tests/rustdoc-js/assoc-type-backtrack.rs
Normal file
@ -0,0 +1,38 @@
|
||||
pub trait MyTrait2<X> {
|
||||
type Output;
|
||||
}
|
||||
|
||||
pub trait MyTrait {
|
||||
type Item;
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
fn fold<B, F>(self, init: B, f: F) -> B where
|
||||
Self: Sized,
|
||||
F: MyTrait2<(B, Self::Item), Output=B>;
|
||||
}
|
||||
|
||||
pub struct Cloned<I>(I);
|
||||
|
||||
impl<'a, T, I> MyTrait for Cloned<I> where
|
||||
T: 'a + Clone,
|
||||
I: MyTrait<Item = &'a T>
|
||||
{
|
||||
type Item = T;
|
||||
fn next(&mut self) -> Option<Self::Item> { loop {} }
|
||||
fn fold<B, F>(self, init: B, f: F) -> B where
|
||||
Self: Sized,
|
||||
F: MyTrait2<(B, Self::Item), Output=B>
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MyFuture {
|
||||
type Output;
|
||||
}
|
||||
|
||||
pub trait MyIntoFuture {
|
||||
type Output;
|
||||
type Fut: MyFuture<Output=Self::Output>;
|
||||
fn into_future(self) -> Self::Fut;
|
||||
fn into_future_2(self, other: Self) -> Self::Fut;
|
||||
}
|
45
tests/rustdoc-js/assoc-type.js
Normal file
45
tests/rustdoc-js/assoc-type.js
Normal file
@ -0,0 +1,45 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
// if I just use generics, then the generics version
|
||||
// and the type binding version both show up
|
||||
{
|
||||
'query': 'iterator<something> -> u32',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
{ 'path': 'assoc_type::my', 'name': 'other_fn' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'iterator<something>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
{ 'path': 'assoc_type::my', 'name': 'other_fn' },
|
||||
],
|
||||
},
|
||||
// if I write an explicit binding, only it shows up
|
||||
{
|
||||
'query': 'iterator<item=something> -> u32',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
],
|
||||
},
|
||||
// case insensitivity
|
||||
{
|
||||
'query': 'iterator<ItEm=sOmEtHiNg> -> u32',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
],
|
||||
},
|
||||
// wrong binding name, no result
|
||||
{
|
||||
'query': 'iterator<something=something> -> u32',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [],
|
||||
},
|
||||
];
|
12
tests/rustdoc-js/assoc-type.rs
Normal file
12
tests/rustdoc-js/assoc-type.rs
Normal file
@ -0,0 +1,12 @@
|
||||
pub fn my_fn<X: Iterator<Item = Something>>(_x: X) -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
pub struct Something;
|
||||
|
||||
pub mod my {
|
||||
pub trait Iterator<T> {}
|
||||
pub fn other_fn<X: Iterator<crate::Something>>(_: X) -> u32 {
|
||||
3
|
||||
}
|
||||
}
|
57
tests/rustdoc-js/gat.js
Normal file
57
tests/rustdoc-js/gat.js
Normal file
@ -0,0 +1,57 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u8> -> u32',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [
|
||||
{ 'path': 'gat', 'name': 'sample' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u8> -> !',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [
|
||||
{ 'path': 'gat', 'name': 'synergy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u8>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'sample' },
|
||||
{ 'path': 'gat', 'name': 'synergy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u32>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'consider' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// This one is arguably a bug, because the way rustdoc
|
||||
// stores GATs in the search index is sloppy, but it's
|
||||
// precise enough to match most of the samples in the
|
||||
// GAT initiative repo
|
||||
'query': 'foo<assoc<u32>=u8>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'consider' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// This one is arguably a bug, because the way rustdoc
|
||||
// stores GATs in the search index is sloppy, but it's
|
||||
// precise enough to match most of the samples in the
|
||||
// GAT initiative repo
|
||||
'query': 'foo<assoc<T>=T>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'integrate' },
|
||||
],
|
||||
},
|
||||
];
|
8
tests/rustdoc-js/gat.rs
Normal file
8
tests/rustdoc-js/gat.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub trait Foo {
|
||||
type Assoc<T>;
|
||||
}
|
||||
|
||||
pub fn sample<X: Foo<Assoc<u8> = u8>>(_: X) -> u32 { loop {} }
|
||||
pub fn synergy(_: impl Foo<Assoc<u8> = u8>) -> ! { loop {} }
|
||||
pub fn consider(_: impl Foo<Assoc<u8> = u32>) -> bool { loop {} }
|
||||
pub fn integrate<T>(_: impl Foo<Assoc<T> = T>) -> T { loop {} }
|
@ -43,4 +43,14 @@ const EXPECTED = [
|
||||
{ 'path': 'never_search', 'name': 'box_uninteresting' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'box<item=!>',
|
||||
'in_args': [],
|
||||
'returned': [],
|
||||
},
|
||||
{
|
||||
'query': 'box<item=never>',
|
||||
'in_args': [],
|
||||
'returned': [],
|
||||
},
|
||||
];
|
||||
|
12
tests/rustdoc-js/trait-methods.js
Normal file
12
tests/rustdoc-js/trait-methods.js
Normal file
@ -0,0 +1,12 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'mytrait<t> -> option<t>',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [
|
||||
{ 'path': 'trait_methods::MyTrait', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
];
|
4
tests/rustdoc-js/trait-methods.rs
Normal file
4
tests/rustdoc-js/trait-methods.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub trait MyTrait {
|
||||
type Item;
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
}
|
Loading…
Reference in New Issue
Block a user