Skip to content

Commit f6a045c

Browse files
committed
rustdoc: search for tuples and unit by type with ()
1 parent a75fed7 commit f6a045c

File tree

9 files changed

+616
-54
lines changed

9 files changed

+616
-54
lines changed

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

+56-11
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,55 @@ will match these queries:
150150

151151
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
152152

153-
Function signature searches also support arrays and slices. The explicit name
154-
`primitive:slice<u8>` and `primitive:array<u8>` can be used to match a slice
155-
or array of bytes, while square brackets `[u8]` will match either one. Empty
156-
square brackets, `[]`, will match any slice or array regardless of what
157-
it contains, while a slice with a type parameter, like `[T]`, will only match
158-
functions that actually operate on generic slices.
153+
### Primitives with Special Syntax
154+
155+
<table>
156+
<thead>
157+
<tr>
158+
<th>Shorthand</th>
159+
<th>Explicit names</th>
160+
</tr>
161+
</thead>
162+
<tbody>
163+
<tr>
164+
<td><code>[]</code></td>
165+
<td><code>primitive:slice</code> and/or <code>primitive:array</code></td>
166+
</tr>
167+
<tr>
168+
<td><code>[T]</code></td>
169+
<td><code>primitive:slice&lt;T&gt;</code> and/or <code>primitive:array&lt;T&gt;</code></td>
170+
</tr>
171+
<tr>
172+
<td><code>()</code></td>
173+
<td><code>primitive:unit</code> and/or <code>primitive:tuple</code></td>
174+
</tr>
175+
<tr>
176+
<td><code>(T)</code></td>
177+
<td><code>T</code></td>
178+
</tr>
179+
<tr>
180+
<td><code>(T,)</code></td>
181+
<td><code>primitive:tuple&lt;T&gt;</code></td>
182+
</tr>
183+
<tr>
184+
<td><code>!</code></td>
185+
<td><code>primitive:never</code></td>
186+
</tr>
187+
</tbody>
188+
</table>
189+
190+
When searching for `[]`, Rustdoc will return search results with either slices
191+
or arrays. If you know which one you want, you can force it to return results
192+
for `primitive:slice` or `primitive:array` using the explicit name syntax.
193+
Empty square brackets, `[]`, will match any slice or array regardless of what
194+
it contains, or an item type can be provided, such as `[u8]` or `[T]`, to
195+
explicitly find functions that operate on byte slices or generic slices,
196+
respectively.
197+
198+
A single type expression wrapped in parens is the same as that type expression,
199+
since parens act as the grouping operator. If they're empty, though, they will
200+
match both `unit` and `tuple`, and if there's more than one type (or a trailing
201+
or leading comma) it is the same as `primitive:tuple<...>`.
159202

160203
### Limitations and quirks of type-based search
161204

@@ -188,11 +231,10 @@ Most of these limitations should be addressed in future version of Rustdoc.
188231
that you don't want a type parameter, you can force it to match
189232
something else by giving it a different prefix like `struct:T`.
190233

191-
* It's impossible to search for references, pointers, or tuples. The
234+
* It's impossible to search for references or pointers. The
192235
wrapped types can be searched for, so a function that takes `&File` can
193236
be found with `File`, but you'll get a parse error when typing an `&`
194-
into the search field. Similarly, `Option<(T, U)>` can be matched with
195-
`Option<T, U>`, but `(` will give a parse error.
237+
into the search field.
196238

197239
* Searching for lifetimes is not supported.
198240

@@ -216,8 +258,9 @@ Item filters can be used in both name-based and type signature-based searches.
216258
```text
217259
ident = *(ALPHA / DIGIT / "_")
218260
path = ident *(DOUBLE-COLON ident) [!]
219-
slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
220-
arg = [type-filter *WS COLON *WS] (path [generics] / slice / [!])
261+
slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
262+
tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
263+
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / [!])
221264
type-sep = COMMA/WS *(COMMA/WS)
222265
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
223266
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
@@ -263,6 +306,8 @@ OPEN-ANGLE-BRACKET = "<"
263306
CLOSE-ANGLE-BRACKET = ">"
264307
OPEN-SQUARE-BRACKET = "["
265308
CLOSE-SQUARE-BRACKET = "]"
309+
OPEN-PAREN = "("
310+
CLOSE-PAREN = ")"
266311
COLON = ":"
267312
DOUBLE-COLON = "::"
268313
QUOTE = %x22

src/librustdoc/html/render/search_index.rs

+3
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,9 @@ fn get_index_type_id(
581581
// The type parameters are converted to generics in `simplify_fn_type`
582582
clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
583583
clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
584+
clean::Tuple(ref n) if n.is_empty() => {
585+
Some(RenderTypeId::Primitive(clean::PrimitiveType::Unit))
586+
}
584587
clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)),
585588
clean::QPath(ref data) => {
586589
if data.self_type.is_self_type()

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

+71-29
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,18 @@ function initSearch(rawSearchIndex) {
260260
* Special type name IDs for searching by both array and slice (`[]` syntax).
261261
*/
262262
let typeNameIdOfArrayOrSlice;
263+
/**
264+
* Special type name IDs for searching by tuple.
265+
*/
266+
let typeNameIdOfTuple;
267+
/**
268+
* Special type name IDs for searching by unit.
269+
*/
270+
let typeNameIdOfUnit;
271+
/**
272+
* Special type name IDs for searching by both tuple and unit (`()` syntax).
273+
*/
274+
let typeNameIdOfTupleOrUnit;
263275

264276
/**
265277
* Add an item to the type Name->ID map, or, if one already exists, use it.
@@ -295,11 +307,7 @@ function initSearch(rawSearchIndex) {
295307
}
296308

297309
function isEndCharacter(c) {
298-
return "=,>-]".indexOf(c) !== -1;
299-
}
300-
301-
function isErrorCharacter(c) {
302-
return "()".indexOf(c) !== -1;
310+
return "=,>-])".indexOf(c) !== -1;
303311
}
304312

305313
function itemTypeFromName(typename) {
@@ -585,8 +593,6 @@ function initSearch(rawSearchIndex) {
585593
throw ["Unexpected ", "!", ": it can only be at the end of an ident"];
586594
}
587595
foundExclamation = parserState.pos;
588-
} else if (isErrorCharacter(c)) {
589-
throw ["Unexpected ", c];
590596
} else if (isPathSeparator(c)) {
591597
if (c === ":") {
592598
if (!isPathStart(parserState)) {
@@ -616,11 +622,14 @@ function initSearch(rawSearchIndex) {
616622
}
617623
} else if (
618624
c === "[" ||
625+
c === "(" ||
619626
isEndCharacter(c) ||
620627
isSpecialStartCharacter(c) ||
621628
isSeparatorCharacter(c)
622629
) {
623630
break;
631+
} else if (parserState.pos > 0) {
632+
throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]];
624633
} else {
625634
throw ["Unexpected ", c];
626635
}
@@ -661,43 +670,56 @@ function initSearch(rawSearchIndex) {
661670
skipWhitespace(parserState);
662671
let start = parserState.pos;
663672
let end;
664-
if (parserState.userQuery[parserState.pos] === "[") {
673+
if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {
674+
let endChar = ")";
675+
let name = "()";
676+
let friendlyName = "tuple";
677+
678+
if (parserState.userQuery[parserState.pos] === "[") {
679+
endChar = "]";
680+
name = "[]";
681+
friendlyName = "slice";
682+
}
665683
parserState.pos += 1;
666-
getItemsBefore(query, parserState, generics, "]");
684+
const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);
667685
const typeFilter = parserState.typeFilter;
668686
const isInBinding = parserState.isInBinding;
669687
if (typeFilter !== null && typeFilter !== "primitive") {
670688
throw [
671689
"Invalid search type: primitive ",
672-
"[]",
690+
name,
673691
" and ",
674692
typeFilter,
675693
" both specified",
676694
];
677695
}
678696
parserState.typeFilter = null;
679697
parserState.isInBinding = null;
680-
parserState.totalElems += 1;
681-
if (isInGenerics) {
682-
parserState.genericsElems += 1;
683-
}
684698
for (const gen of generics) {
685699
if (gen.bindingName !== null) {
686-
throw ["Type parameter ", "=", " cannot be within slice ", "[]"];
700+
throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];
687701
}
688702
}
689-
elems.push({
690-
name: "[]",
691-
id: null,
692-
fullPath: ["[]"],
693-
pathWithoutLast: [],
694-
pathLast: "[]",
695-
normalizedPathLast: "[]",
696-
generics,
697-
typeFilter: "primitive",
698-
bindingName: isInBinding,
699-
bindings: new Map(),
700-
});
703+
if (name === "()" && !foundSeparator && generics.length === 1 && typeFilter === null) {
704+
elems.push(generics[0]);
705+
} else {
706+
parserState.totalElems += 1;
707+
if (isInGenerics) {
708+
parserState.genericsElems += 1;
709+
}
710+
elems.push({
711+
name: name,
712+
id: null,
713+
fullPath: [name],
714+
pathWithoutLast: [],
715+
pathLast: name,
716+
normalizedPathLast: name,
717+
generics,
718+
bindings: new Map(),
719+
typeFilter: "primitive",
720+
bindingName: isInBinding,
721+
});
722+
}
701723
} else {
702724
const isStringElem = parserState.userQuery[start] === "\"";
703725
// We handle the strings on their own mostly to make code easier to follow.
@@ -770,9 +792,11 @@ function initSearch(rawSearchIndex) {
770792
* @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
771793
* @param {string} endChar - This function will stop when it'll encounter this
772794
* character.
795+
* @returns {{foundSeparator: bool}}
773796
*/
774797
function getItemsBefore(query, parserState, elems, endChar) {
775798
let foundStopChar = true;
799+
let foundSeparator = false;
776800
let start = parserState.pos;
777801

778802
// If this is a generic, keep the outer item's type filter around.
@@ -786,6 +810,8 @@ function initSearch(rawSearchIndex) {
786810
extra = "<";
787811
} else if (endChar === "]") {
788812
extra = "[";
813+
} else if (endChar === ")") {
814+
extra = "(";
789815
} else if (endChar === "") {
790816
extra = "->";
791817
} else {
@@ -802,6 +828,7 @@ function initSearch(rawSearchIndex) {
802828
} else if (isSeparatorCharacter(c)) {
803829
parserState.pos += 1;
804830
foundStopChar = true;
831+
foundSeparator = true;
805832
continue;
806833
} else if (c === ":" && isPathStart(parserState)) {
807834
throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
@@ -879,6 +906,8 @@ function initSearch(rawSearchIndex) {
879906

880907
parserState.typeFilter = oldTypeFilter;
881908
parserState.isInBinding = oldIsInBinding;
909+
910+
return { foundSeparator };
882911
}
883912

884913
/**
@@ -926,6 +955,8 @@ function initSearch(rawSearchIndex) {
926955
break;
927956
}
928957
throw ["Unexpected ", c, " (did you mean ", "->", "?)"];
958+
} else if (parserState.pos > 0) {
959+
throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]];
929960
}
930961
throw ["Unexpected ", c];
931962
} else if (c === ":" && !isPathStart(parserState)) {
@@ -1599,6 +1630,11 @@ function initSearch(rawSearchIndex) {
15991630
) {
16001631
// [] matches primitive:array or primitive:slice
16011632
// if it matches, then we're fine, and this is an appropriate match candidate
1633+
} else if (queryElem.id === typeNameIdOfTupleOrUnit &&
1634+
(fnType.id === typeNameIdOfTuple || fnType.id === typeNameIdOfUnit)
1635+
) {
1636+
// () matches primitive:tuple or primitive:unit
1637+
// if it matches, then we're fine, and this is an appropriate match candidate
16021638
} else if (fnType.id !== queryElem.id || queryElem.id === null) {
16031639
return false;
16041640
}
@@ -1792,7 +1828,7 @@ function initSearch(rawSearchIndex) {
17921828
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
17931829
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
17941830
// special case
1795-
elem.id !== typeNameIdOfArrayOrSlice
1831+
elem.id !== typeNameIdOfArrayOrSlice && elem.id !== typeNameIdOfTupleOrUnit
17961832
) {
17971833
return row.id === elem.id || checkIfInList(
17981834
row.generics,
@@ -2822,12 +2858,15 @@ ${item.displayPath}<span class="${type}">${name}</span>\
28222858
*/
28232859
function buildFunctionTypeFingerprint(type, output, fps) {
28242860
let input = type.id;
2825-
// All forms of `[]` get collapsed down to one thing in the bloom filter.
2861+
// All forms of `[]`/`()` get collapsed down to one thing in the bloom filter.
28262862
// Differentiating between arrays and slices, if the user asks for it, is
28272863
// still done in the matching algorithm.
28282864
if (input === typeNameIdOfArray || input === typeNameIdOfSlice) {
28292865
input = typeNameIdOfArrayOrSlice;
28302866
}
2867+
if (input === typeNameIdOfTuple || input === typeNameIdOfUnit) {
2868+
input = typeNameIdOfTupleOrUnit;
2869+
}
28312870
// http://burtleburtle.net/bob/hash/integer.html
28322871
// ~~ is toInt32. It's used before adding, so
28332872
// the number stays in safe integer range.
@@ -2922,7 +2961,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
29222961
// that can be searched using `[]` syntax.
29232962
typeNameIdOfArray = buildTypeMapIndex("array");
29242963
typeNameIdOfSlice = buildTypeMapIndex("slice");
2964+
typeNameIdOfTuple = buildTypeMapIndex("tuple");
2965+
typeNameIdOfUnit = buildTypeMapIndex("unit");
29252966
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
2967+
typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
29262968

29272969
// Function type fingerprints are 128-bit bloom filters that are used to
29282970
// estimate the distance between function and query.

tests/rustdoc-js-std/parser-errors.js

+4-13
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const PARSED = [
2424
original: "-> *",
2525
returned: [],
2626
userQuery: "-> *",
27-
error: "Unexpected `*`",
27+
error: "Unexpected `*` after ` `",
2828
},
2929
{
3030
query: 'a<"P">',
@@ -107,23 +107,14 @@ const PARSED = [
107107
userQuery: "a<::a>",
108108
error: "Unexpected `::`: paths cannot start with `::`",
109109
},
110-
{
111-
query: "((a))",
112-
elems: [],
113-
foundElems: 0,
114-
original: "((a))",
115-
returned: [],
116-
userQuery: "((a))",
117-
error: "Unexpected `(`",
118-
},
119110
{
120111
query: "(p -> p",
121112
elems: [],
122113
foundElems: 0,
123114
original: "(p -> p",
124115
returned: [],
125116
userQuery: "(p -> p",
126-
error: "Unexpected `(`",
117+
error: "Unexpected `-` after `(`",
127118
},
128119
{
129120
query: "::a::b",
@@ -204,7 +195,7 @@ const PARSED = [
204195
original: "a (b:",
205196
returned: [],
206197
userQuery: "a (b:",
207-
error: "Unexpected `(`",
198+
error: "Expected `,`, `:` or `->`, found `(`",
208199
},
209200
{
210201
query: "_:",
@@ -249,7 +240,7 @@ const PARSED = [
249240
original: "ab'",
250241
returned: [],
251242
userQuery: "ab'",
252-
error: "Unexpected `'`",
243+
error: "Unexpected `'` after `b`",
253244
},
254245
{
255246
query: "a->",

0 commit comments

Comments
 (0)