Skip to content

Commit 3f68468

Browse files
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.
2 parents ae9c330 + 4cf06e8 commit 3f68468

18 files changed

+1199
-462
lines changed

src/doc/rustdoc/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- [Command-line arguments](command-line-arguments.md)
55
- [How to read rustdoc output](how-to-read-rustdoc.md)
66
- [In-doc settings](read-documentation/in-doc-settings.md)
7+
- [Search](read-documentation/search.md)
78
- [How to write documentation](how-to-write-documentation.md)
89
- [What to include (and exclude)](write-documentation/what-to-include.md)
910
- [The `#[doc]` attribute](write-documentation/the-doc-attribute.md)

src/doc/rustdoc/src/how-to-read-rustdoc.md

+5-50
Original file line numberDiff line numberDiff line change
@@ -75,56 +75,11 @@ or the current item whose documentation is being displayed.
7575
## The Theme Picker and Search Interface
7676

7777
When viewing `rustdoc`'s output in a browser with JavaScript enabled,
78-
a dynamic interface appears at the top of the page composed of the search
79-
interface, help screen, and options.
80-
81-
### The Search Interface
82-
83-
Typing in the search bar instantly searches the available documentation for
84-
the string entered with a fuzzy matching algorithm that is tolerant of minor
85-
typos.
86-
87-
By default, the search results given are "In Names",
88-
meaning that the fuzzy match is made against the names of items.
89-
Matching names are shown on the left, and the first few words of their
90-
descriptions are given on the right.
91-
By clicking an item, you will navigate to its particular documentation.
92-
93-
There are two other sets of results, shown as tabs in the search results pane.
94-
"In Parameters" shows matches for the string in the types of parameters to
95-
functions, and "In Return Types" shows matches in the return types of functions.
96-
Both are very useful when looking for a function whose name you can't quite
97-
bring to mind when you know the type you have or want.
98-
99-
Names in the search interface can be prefixed with an item type followed by a
100-
colon (such as `mod:`) to restrict the results to just that kind of item. Also,
101-
searching for `println!` will search for a macro named `println`, just like
102-
searching for `macro:println` does.
103-
104-
Function signature searches can query generics, wrapped in angle brackets, and
105-
traits are normalized like types in the search engine. For example, a function
106-
with the signature `fn my_function<I: Iterator<Item=u32>>(input: I) -> usize`
107-
can be matched with the following queries:
108-
109-
* `Iterator<u32> -> usize`
110-
* `trait:Iterator<primitive:u32> -> primitive:usize`
111-
* `Iterator -> usize`
112-
113-
Generics and function parameters are order-agnostic, but sensitive to nesting
114-
and number of matches. For example, a function with the signature
115-
`fn read_all(&mut self: impl Read) -> Result<Vec<u8>, Error>`
116-
will match these queries:
117-
118-
* `Read -> Result<Vec<u8>, Error>`
119-
* `Read -> Result<Error, Vec>`
120-
* `Read -> Result<Vec<u8>>`
121-
122-
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
123-
124-
Function signature searches also support arrays and slices. The explicit name
125-
`primitive:slice<u8>` and `primitive:array<u8>` can be used to match a slice
126-
or array of bytes, while square brackets `[u8]` will match either one. Empty
127-
square brackets, `[]`, will match any slice regardless of what it contains.
78+
a dynamic interface appears at the top of the page composed of the [search]
79+
interface, help screen, and [options].
80+
81+
[options]: read-documentation/in-doc-settings.html
82+
[search]: read-documentation/search.md
12883

12984
Paths are supported as well, you can look for `Vec::new` or `Option::Some` or
13085
even `module::module_child::another_child::struct::field`. Whitespace characters
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# Rustdoc search
2+
3+
Typing in the search bar instantly searches the available documentation,
4+
matching either the name and path of an item, or a function's approximate
5+
type signature.
6+
7+
## Search By Name
8+
9+
To search by the name of an item (items include modules, types, traits,
10+
functions, and macros), write its name or path. As a special case, the parts
11+
of a path that normally get divided by `::` double colons can instead be
12+
separated by spaces. For example:
13+
14+
* [`vec new`] and [`vec::new`] both show the function `std::vec::Vec::new`
15+
as a result.
16+
* [`vec`], [`vec vec`], [`std::vec`], and [`std::vec::Vec`] all include the struct
17+
`std::vec::Vec` itself in the results (and all but the last one also
18+
include the module in the results).
19+
20+
[`vec new`]: ../../std/vec/struct.Vec.html?search=vec%20new&filter-crate=std
21+
[`vec::new`]: ../../std/vec/struct.Vec.html?search=vec::new&filter-crate=std
22+
[`vec`]: ../../std/vec/struct.Vec.html?search=vec&filter-crate=std
23+
[`vec vec`]: ../../std/vec/struct.Vec.html?search=vec%20vec&filter-crate=std
24+
[`std::vec`]: ../../std/vec/struct.Vec.html?search=std::vec&filter-crate=std
25+
[`std::vec::Vec`]: ../../std/vec/struct.Vec.html?search=std::vec::Vec&filter-crate=std
26+
[`std::vec::Vec`]: ../../std/vec/struct.Vec.html?search=std::vec::Vec&filter-crate=std
27+
28+
As a quick way to trim down the list of results, there's a drop-down selector
29+
below the search input, labeled "Results in \[std\]". Clicking it can change
30+
which crate is being searched.
31+
32+
Rustdoc uses a fuzzy matching function that can tolerate typos for this,
33+
though it's based on the length of the name that's typed in, so a good example
34+
of how this works would be [`HahsMap`]. To avoid this, wrap the item in quotes,
35+
searching for `"HahsMap"` (in this example, no results will be returned).
36+
37+
[`HahsMap`]: ../../std/collections/struct.HashMap.html?search=HahsMap&filter-crate=std
38+
39+
### Tabs in the Search By Name interface
40+
41+
In fact, using [`HahsMap`] again as the example, it tells you that you're
42+
using "In Names" by default, but also lists two other tabs below the crate
43+
drop-down: "In Parameters" and "In Return Types".
44+
45+
These two tabs are lists of functions, defined on the closest matching type
46+
to the search (for `HahsMap`, it loudly auto-corrects to `hashmap`). This
47+
auto-correct only kicks in if nothing is found that matches the literal.
48+
49+
These tabs are not just methods. For example, searching the alloc crate for
50+
[`Layout`] also lists functions that accept layouts even though they're
51+
methods on the allocator or free functions.
52+
53+
[`Layout`]: ../../alloc/index.html?search=Layout&filter-crate=alloc
54+
55+
## Searching By Type Signature for functions
56+
57+
If you know more specifically what the function you want to look at does,
58+
Rustdoc can search by more than one type at once in the parameters and return
59+
value. Multiple parameters are separated by `,` commas, and the return value
60+
is written with after a `->` arrow.
61+
62+
Before describing the syntax in more detail, here's a few sample searches of
63+
the standard library and functions that are included in the results list:
64+
65+
| Query | Results |
66+
|-------|--------|
67+
| [`usize -> vec`][] | `slice::repeat` and `Vec::with_capacity` |
68+
| [`vec, vec -> bool`][] | `Vec::eq` |
69+
| [`option<T>, fnonce -> option<U>`][] | `Option::map` and `Option::and_then` |
70+
| [`option<T>, fnonce -> option<T>`][] | `Option::filter` and `Option::inspect` |
71+
| [`option -> default`][] | `Option::unwrap_or_default` |
72+
| [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
73+
| [`any -> !`][] | `panic::panic_any` |
74+
| [`vec::intoiter<T> -> [T]`][iterasslice] | `IntoIter::as_slice` and `IntoIter::next_chunk` |
75+
76+
[`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std
77+
[`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std
78+
[`option<T>, fnonce -> option<U>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<U>&filter-crate=std
79+
[`option<T>, fnonce -> option<T>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<T>&filter-crate=std
80+
[`option -> default`]: ../../std/vec/struct.Vec.html?search=option%20-%3E%20default&filter-crate=std
81+
[`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std
82+
[stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std
83+
[iterasslice]: ../../std/vec/struct.Vec.html?search=vec%3A%3Aintoiter<T>%20->%20[T]&filter-crate=std
84+
85+
### How type-based search works
86+
87+
In a complex type-based search, Rustdoc always treats every item's name as literal.
88+
If a name is used and nothing in the docs matches the individual item, such as
89+
a typo-ed [`uize -> vec`][] search, the item `uize` is treated as a generic
90+
type parameter (resulting in `vec::from` and other generic vec constructors).
91+
92+
[`uize -> vec`]: ../../std/vec/struct.Vec.html?search=uize%20-%3E%20vec&filter-crate=std
93+
94+
After deciding which items are type parameters and which are actual types, it
95+
then searches by matching up the function parameters (written before the `->`)
96+
and the return types (written after the `->`). Type matching is order-agnostic,
97+
and allows items to be left out of the query, but items that are present in the
98+
query must be present in the function for it to match.
99+
100+
Function signature searches can query generics, wrapped in angle brackets, and
101+
traits will be normalized like types in the search engine if no type parameters
102+
match them. For example, a function with the signature
103+
`fn my_function<I: Iterator<Item=u32>>(input: I) -> usize`
104+
can be matched with the following queries:
105+
106+
* `Iterator<u32> -> usize`
107+
* `Iterator -> usize`
108+
109+
Generics and function parameters are order-agnostic, but sensitive to nesting
110+
and number of matches. For example, a function with the signature
111+
`fn read_all(&mut self: impl Read) -> Result<Vec<u8>, Error>`
112+
will match these queries:
113+
114+
* `Read -> Result<Vec<u8>, Error>`
115+
* `Read -> Result<Error, Vec>`
116+
* `Read -> Result<Vec<u8>>`
117+
118+
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
119+
120+
Function signature searches also support arrays and slices. The explicit name
121+
`primitive:slice<u8>` and `primitive:array<u8>` can be used to match a slice
122+
or array of bytes, while square brackets `[u8]` will match either one. Empty
123+
square brackets, `[]`, will match any slice or array regardless of what
124+
it contains, while a slice with a type parameter, like `[T]`, will only match
125+
functions that actually operate on generic slices.
126+
127+
### Limitations and quirks of type-based search
128+
129+
Type-based search is still a buggy, experimental, work-in-progress feature.
130+
Most of these limitations should be addressed in future version of Rustdoc.
131+
132+
* There's no way to write trait constraints on generic parameters.
133+
You can name traits directly, and if there's a type parameter
134+
with that bound, it'll match, but `option<T> -> T where T: Default`
135+
cannot be precisely searched for (use `option<Default> -> Default`).
136+
137+
* Type parameters match type parameters, such that `Option<A>` matches
138+
`Option<T>`, but never match concrete types in function signatures.
139+
A trait named as if it were a type, such as `Option<Read>`, will match
140+
a type parameter constrained by that trait, such as
141+
`Option<T> where T: Read`, as well as matching `dyn Trait` and
142+
`impl Trait`.
143+
144+
* `impl Trait` in argument position is treated exactly like a type
145+
parameter, but in return position it will not match type parameters.
146+
147+
* Any type named in a complex type-based search will be assumed to be a
148+
type parameter if nothing matching the name exactly is found. If you
149+
want to force a type parameter, write `generic:T` and it will be used
150+
as a type parameter even if a matching name is found. If you know
151+
that you don't want a type parameter, you can force it to match
152+
something else by giving it a different prefix like `struct:T`.
153+
154+
* It's impossible to search for references, pointers, or tuples. The
155+
wrapped types can be searched for, so a function that takes `&File` can
156+
be found with `File`, but you'll get a parse error when typing an `&`
157+
into the search field. Similarly, `Option<(T, U)>` can be matched with
158+
`Option<T, U>`, but `(` will give a parse error.
159+
160+
* Searching for lifetimes is not supported.
161+
162+
* It's impossible to search for closures based on their parameters or
163+
return values.
164+
165+
* It's impossible to search based on the length of an array.
166+
167+
## Item filtering
168+
169+
Names in the search interface can be prefixed with an item type followed by a
170+
colon (such as `mod:`) to restrict the results to just that kind of item. Also,
171+
searching for `println!` will search for a macro named `println`, just like
172+
searching for `macro:println` does. The complete list of available filters is
173+
given under the <kbd>?</kbd> Help area, and in the detailed syntax below.
174+
175+
Item filters can be used in both name-based and type signature-based searches.
176+
177+
## Search query syntax
178+
179+
```text
180+
ident = *(ALPHA / DIGIT / "_")
181+
path = ident *(DOUBLE-COLON ident) [!]
182+
slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
183+
arg = [type-filter *WS COLON *WS] (path [generics] / slice / [!])
184+
type-sep = COMMA/WS *(COMMA/WS)
185+
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
186+
generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
187+
CLOSE-ANGLE-BRACKET
188+
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
189+
190+
exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
191+
type-search = [ nonempty-arg-list ] [ return-args ]
192+
193+
query = *WS (exact-search / type-search) *WS
194+
195+
type-filter = (
196+
"mod" /
197+
"externcrate" /
198+
"import" /
199+
"struct" /
200+
"enum" /
201+
"fn" /
202+
"type" /
203+
"static" /
204+
"trait" /
205+
"impl" /
206+
"tymethod" /
207+
"method" /
208+
"structfield" /
209+
"variant" /
210+
"macro" /
211+
"primitive" /
212+
"associatedtype" /
213+
"constant" /
214+
"associatedconstant" /
215+
"union" /
216+
"foreigntype" /
217+
"keyword" /
218+
"existential" /
219+
"attr" /
220+
"derive" /
221+
"traitalias" /
222+
"generic")
223+
224+
OPEN-ANGLE-BRACKET = "<"
225+
CLOSE-ANGLE-BRACKET = ">"
226+
OPEN-SQUARE-BRACKET = "["
227+
CLOSE-SQUARE-BRACKET = "]"
228+
COLON = ":"
229+
DOUBLE-COLON = "::"
230+
QUOTE = %x22
231+
COMMA = ","
232+
RETURN-ARROW = "->"
233+
234+
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
235+
DIGIT = %x30-39
236+
WS = %x09 / " "
237+
```

src/librustdoc/clean/types.rs

-4
Original file line numberDiff line numberDiff line change
@@ -1639,10 +1639,6 @@ impl Type {
16391639
matches!(self, Type::Generic(_))
16401640
}
16411641

1642-
pub(crate) fn is_impl_trait(&self) -> bool {
1643-
matches!(self, Type::ImplTrait(_))
1644-
}
1645-
16461642
pub(crate) fn is_unit(&self) -> bool {
16471643
matches!(self, Type::Tuple(v) if v.is_empty())
16481644
}

src/librustdoc/html/render/mod.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub(crate) struct IndexItem {
101101
pub(crate) path: String,
102102
pub(crate) desc: String,
103103
pub(crate) parent: Option<DefId>,
104-
pub(crate) parent_idx: Option<usize>,
104+
pub(crate) parent_idx: Option<isize>,
105105
pub(crate) search_type: Option<IndexItemFunctionType>,
106106
pub(crate) aliases: Box<[Symbol]>,
107107
pub(crate) deprecation: Option<Deprecation>,
@@ -122,7 +122,10 @@ impl Serialize for RenderType {
122122
let id = match &self.id {
123123
// 0 is a sentinel, everything else is one-indexed
124124
None => 0,
125-
Some(RenderTypeId::Index(idx)) => idx + 1,
125+
// concrete type
126+
Some(RenderTypeId::Index(idx)) if *idx >= 0 => idx + 1,
127+
// generic type parameter
128+
Some(RenderTypeId::Index(idx)) => *idx,
126129
_ => panic!("must convert render types to indexes before serializing"),
127130
};
128131
if let Some(generics) = &self.generics {
@@ -140,14 +143,15 @@ impl Serialize for RenderType {
140143
pub(crate) enum RenderTypeId {
141144
DefId(DefId),
142145
Primitive(clean::PrimitiveType),
143-
Index(usize),
146+
Index(isize),
144147
}
145148

146149
/// Full type of functions/methods in the search index.
147150
#[derive(Debug)]
148151
pub(crate) struct IndexItemFunctionType {
149152
inputs: Vec<RenderType>,
150153
output: Vec<RenderType>,
154+
where_clause: Vec<Vec<RenderType>>,
151155
}
152156

153157
impl Serialize for IndexItemFunctionType {
@@ -170,10 +174,17 @@ impl Serialize for IndexItemFunctionType {
170174
_ => seq.serialize_element(&self.inputs)?,
171175
}
172176
match &self.output[..] {
173-
[] => {}
177+
[] if self.where_clause.is_empty() => {}
174178
[one] if one.generics.is_none() => seq.serialize_element(one)?,
175179
_ => seq.serialize_element(&self.output)?,
176180
}
181+
for constraint in &self.where_clause {
182+
if let [one] = &constraint[..] && one.generics.is_none() {
183+
seq.serialize_element(one)?;
184+
} else {
185+
seq.serialize_element(constraint)?;
186+
}
187+
}
177188
seq.end()
178189
}
179190
}

0 commit comments

Comments
 (0)