Skip to content

Commit da5d870

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

File tree

9 files changed

+615
-53
lines changed

9 files changed

+615
-53
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

+70-28
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,18 @@ function initSearch(rawSearchIndex) {
264264
* Special type name IDs for searching by both array and slice (`[]` syntax).
265265
*/
266266
let typeNameIdOfArrayOrSlice;
267+
/**
268+
* Special type name IDs for searching by tuple.
269+
*/
270+
let typeNameIdOfTuple;
271+
/**
272+
* Special type name IDs for searching by unit.
273+
*/
274+
let typeNameIdOfUnit;
275+
/**
276+
* Special type name IDs for searching by both tuple and unit (`()` syntax).
277+
*/
278+
let typeNameIdOfTupleOrUnit;
267279

268280
/**
269281
* Add an item to the type Name->ID map, or, if one already exists, use it.
@@ -299,11 +311,7 @@ function initSearch(rawSearchIndex) {
299311
}
300312

301313
function isEndCharacter(c) {
302-
return "=,>-]".indexOf(c) !== -1;
303-
}
304-
305-
function isErrorCharacter(c) {
306-
return "()".indexOf(c) !== -1;
314+
return "=,>-])".indexOf(c) !== -1;
307315
}
308316

309317
function itemTypeFromName(typename) {
@@ -586,8 +594,6 @@ function initSearch(rawSearchIndex) {
586594
throw ["Unexpected ", "!", ": it can only be at the end of an ident"];
587595
}
588596
foundExclamation = parserState.pos;
589-
} else if (isErrorCharacter(c)) {
590-
throw ["Unexpected ", c];
591597
} else if (isPathSeparator(c)) {
592598
if (c === ":") {
593599
if (!isPathStart(parserState)) {
@@ -617,11 +623,14 @@ function initSearch(rawSearchIndex) {
617623
}
618624
} else if (
619625
c === "[" ||
626+
c === "(" ||
620627
isEndCharacter(c) ||
621628
isSpecialStartCharacter(c) ||
622629
isSeparatorCharacter(c)
623630
) {
624631
break;
632+
} else if (parserState.pos > 0) {
633+
throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]];
625634
} else {
626635
throw ["Unexpected ", c];
627636
}
@@ -662,42 +671,55 @@ function initSearch(rawSearchIndex) {
662671
skipWhitespace(parserState);
663672
let start = parserState.pos;
664673
let end;
665-
if (parserState.userQuery[parserState.pos] === "[") {
674+
if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {
675+
let endChar = ")";
676+
let name = "()";
677+
let friendlyName = "tuple";
678+
679+
if (parserState.userQuery[parserState.pos] === "[") {
680+
endChar = "]";
681+
name = "[]";
682+
friendlyName = "slice";
683+
}
666684
parserState.pos += 1;
667-
getItemsBefore(query, parserState, generics, "]");
685+
const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);
668686
const typeFilter = parserState.typeFilter;
669687
const isInBinding = parserState.isInBinding;
670688
if (typeFilter !== null && typeFilter !== "primitive") {
671689
throw [
672690
"Invalid search type: primitive ",
673-
"[]",
691+
name,
674692
" and ",
675693
typeFilter,
676694
" both specified",
677695
];
678696
}
679697
parserState.typeFilter = null;
680698
parserState.isInBinding = null;
681-
parserState.totalElems += 1;
682-
if (isInGenerics) {
683-
parserState.genericsElems += 1;
684-
}
685699
for (const gen of generics) {
686700
if (gen.bindingName !== null) {
687-
throw ["Type parameter ", "=", " cannot be within slice ", "[]"];
701+
throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];
688702
}
689703
}
690-
elems.push({
691-
name: "[]",
692-
id: null,
693-
fullPath: ["[]"],
694-
pathWithoutLast: [],
695-
pathLast: "[]",
696-
generics,
697-
typeFilter: "primitive",
698-
bindingName: isInBinding,
699-
bindings: new Map(),
700-
});
704+
if (name === "()" && !foundSeparator && generics.length === 1 && typeFilter === null) {
705+
elems.push(generics[0]);
706+
} else {
707+
parserState.totalElems += 1;
708+
if (isInGenerics) {
709+
parserState.genericsElems += 1;
710+
}
711+
elems.push({
712+
name: name,
713+
id: null,
714+
fullPath: [name],
715+
pathWithoutLast: [],
716+
pathLast: 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)) {
@@ -1600,6 +1631,11 @@ function initSearch(rawSearchIndex) {
16001631
) {
16011632
// [] matches primitive:array or primitive:slice
16021633
// if it matches, then we're fine, and this is an appropriate match candidate
1634+
} else if (queryElem.id === typeNameIdOfTupleOrUnit &&
1635+
(fnType.id === typeNameIdOfTuple || fnType.id === typeNameIdOfUnit)
1636+
) {
1637+
// () matches primitive:tuple or primitive:unit
1638+
// if it matches, then we're fine, and this is an appropriate match candidate
16031639
} else if (fnType.id !== queryElem.id || queryElem.id === null) {
16041640
return false;
16051641
}
@@ -1793,7 +1829,7 @@ function initSearch(rawSearchIndex) {
17931829
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
17941830
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
17951831
// special case
1796-
elem.id !== typeNameIdOfArrayOrSlice
1832+
elem.id !== typeNameIdOfArrayOrSlice && elem.id !== typeNameIdOfTupleOrUnit
17971833
) {
17981834
return row.id === elem.id || checkIfInList(
17991835
row.generics,
@@ -2834,12 +2870,15 @@ ${item.displayPath}<span class="${type}">${name}</span>\
28342870
*/
28352871
function buildFunctionTypeFingerprint(type, output, fps) {
28362872
let input = type.id;
2837-
// All forms of `[]` get collapsed down to one thing in the bloom filter.
2873+
// All forms of `[]`/`()` get collapsed down to one thing in the bloom filter.
28382874
// Differentiating between arrays and slices, if the user asks for it, is
28392875
// still done in the matching algorithm.
28402876
if (input === typeNameIdOfArray || input === typeNameIdOfSlice) {
28412877
input = typeNameIdOfArrayOrSlice;
28422878
}
2879+
if (input === typeNameIdOfTuple || input === typeNameIdOfUnit) {
2880+
input = typeNameIdOfTupleOrUnit;
2881+
}
28432882
// http://burtleburtle.net/bob/hash/integer.html
28442883
// ~~ is toInt32. It's used before adding, so
28452884
// the number stays in safe integer range.
@@ -2940,7 +2979,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
29402979
// that can be searched using `[]` syntax.
29412980
typeNameIdOfArray = buildTypeMapIndex("array");
29422981
typeNameIdOfSlice = buildTypeMapIndex("slice");
2982+
typeNameIdOfTuple = buildTypeMapIndex("tuple");
2983+
typeNameIdOfUnit = buildTypeMapIndex("unit");
29432984
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
2985+
typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
29442986

29452987
// Function type fingerprints are 128-bit bloom filters that are used to
29462988
// 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)