Skip to content

Commit de1cb5c

Browse files
committed
rustdoc: search for tuples and unit by type with ()
1 parent 06d1afe commit de1cb5c

File tree

8 files changed

+561
-50
lines changed

8 files changed

+561
-50
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

+1
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ 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() => Some(RenderTypeId::Primitive(clean::PrimitiveType::Unit)),
584585
clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)),
585586
clean::QPath(ref data) => {
586587
if data.self_type.is_self_type()

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

+56-27
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.
@@ -296,17 +308,13 @@ function initSearch(rawSearchIndex) {
296308
}
297309

298310
function isEndCharacter(c) {
299-
return "=,>-]".indexOf(c) !== -1;
311+
return "=,>-])".indexOf(c) !== -1;
300312
}
301313

302314
function isStopCharacter(c) {
303315
return isEndCharacter(c);
304316
}
305317

306-
function isErrorCharacter(c) {
307-
return "()".indexOf(c) !== -1;
308-
}
309-
310318
function itemTypeFromName(typename) {
311319
const index = itemTypes.findIndex(i => i === typename);
312320
if (index < 0) {
@@ -592,8 +600,6 @@ function initSearch(rawSearchIndex) {
592600
throw ["Unexpected ", "!", ": it can only be at the end of an ident"];
593601
}
594602
foundExclamation = parserState.pos;
595-
} else if (isErrorCharacter(c)) {
596-
throw ["Unexpected ", c];
597603
} else if (isPathSeparator(c)) {
598604
if (c === ":") {
599605
if (!isPathStart(parserState)) {
@@ -623,6 +629,7 @@ function initSearch(rawSearchIndex) {
623629
}
624630
} else if (
625631
c === "[" ||
632+
c === "(" ||
626633
c === "=" ||
627634
isStopCharacter(c) ||
628635
isSpecialStartCharacter(c) ||
@@ -669,42 +676,49 @@ function initSearch(rawSearchIndex) {
669676
skipWhitespace(parserState);
670677
let start = parserState.pos;
671678
let end;
672-
if (parserState.userQuery[parserState.pos] === "[") {
679+
if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {
680+
const endChar = parserState.userQuery[parserState.pos] === "[" ? "]" : ")";
681+
const name = parserState.userQuery[parserState.pos] === "[" ? "[]" : "()";
682+
const friendlyName = parserState.userQuery[parserState.pos] === "[" ? "slice" : "tuple";
673683
parserState.pos += 1;
674-
getItemsBefore(query, parserState, generics, "]");
684+
const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);
675685
const typeFilter = parserState.typeFilter;
676686
const isInBinding = parserState.isInBinding;
677687
if (typeFilter !== null && typeFilter !== "primitive") {
678688
throw [
679689
"Invalid search type: primitive ",
680-
"[]",
690+
name,
681691
" and ",
682692
typeFilter,
683693
" both specified",
684694
];
685695
}
686696
parserState.typeFilter = null;
687697
parserState.isInBinding = null;
688-
parserState.totalElems += 1;
689-
if (isInGenerics) {
690-
parserState.genericsElems += 1;
691-
}
692698
for (const gen of generics) {
693699
if (gen.bindingName !== null) {
694-
throw ["Type parameter ", "=", " cannot be within slice ", "[]"];
700+
throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];
695701
}
696702
}
697-
elems.push({
698-
name: "[]",
699-
id: null,
700-
fullPath: ["[]"],
701-
pathWithoutLast: [],
702-
pathLast: "[]",
703-
generics,
704-
typeFilter: "primitive",
705-
bindingName: isInBinding,
706-
bindings: new Map(),
707-
});
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+
generics,
717+
bindings: new Map(),
718+
typeFilter: "primitive",
719+
bindingName: isInBinding,
720+
});
721+
}
708722
} else {
709723
const isStringElem = parserState.userQuery[start] === "\"";
710724
// We handle the strings on their own mostly to make code easier to follow.
@@ -777,9 +791,11 @@ function initSearch(rawSearchIndex) {
777791
* @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
778792
* @param {string} endChar - This function will stop when it'll encounter this
779793
* character.
794+
* @returns {{foundSeparator: bool}}
780795
*/
781796
function getItemsBefore(query, parserState, elems, endChar) {
782797
let foundStopChar = true;
798+
let foundSeparator = false;
783799
let start = parserState.pos;
784800

785801
// If this is a generic, keep the outer item's type filter around.
@@ -793,6 +809,8 @@ function initSearch(rawSearchIndex) {
793809
extra = "<";
794810
} else if (endChar === "]") {
795811
extra = "[";
812+
} else if (endChar === ")") {
813+
extra = "(";
796814
} else if (endChar === "") {
797815
extra = "->";
798816
} else {
@@ -809,6 +827,7 @@ function initSearch(rawSearchIndex) {
809827
} else if (isSeparatorCharacter(c)) {
810828
parserState.pos += 1;
811829
foundStopChar = true;
830+
foundSeparator = true;
812831
continue;
813832
} else if (c === ":" && isPathStart(parserState)) {
814833
throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
@@ -886,6 +905,8 @@ function initSearch(rawSearchIndex) {
886905

887906
parserState.typeFilter = oldTypeFilter;
888907
parserState.isInBinding = oldIsInBinding;
908+
909+
return { foundSeparator };
889910
}
890911

891912
/**
@@ -1625,6 +1646,11 @@ function initSearch(rawSearchIndex) {
16251646
) {
16261647
// [] matches primitive:array or primitive:slice
16271648
// if it matches, then we're fine, and this is an appropriate match candidate
1649+
} else if (queryElem.id === typeNameIdOfTupleOrUnit &&
1650+
(fnType.id === typeNameIdOfTuple || fnType.id === typeNameIdOfUnit)
1651+
) {
1652+
// () matches primitive:tuple or primitive:unit
1653+
// if it matches, then we're fine, and this is an appropriate match candidate
16281654
} else if (fnType.id !== queryElem.id || queryElem.id === null) {
16291655
return false;
16301656
}
@@ -1807,7 +1833,7 @@ function initSearch(rawSearchIndex) {
18071833
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
18081834
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
18091835
// special case
1810-
elem.id !== typeNameIdOfArrayOrSlice
1836+
elem.id !== typeNameIdOfArrayOrSlice && elem.id !== typeNameIdOfTupleOrUnit
18111837
) {
18121838
return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
18131839
}
@@ -2861,7 +2887,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
28612887
// that can be searched using `[]` syntax.
28622888
typeNameIdOfArray = buildTypeMapIndex("array");
28632889
typeNameIdOfSlice = buildTypeMapIndex("slice");
2890+
typeNameIdOfTuple = buildTypeMapIndex("tuple");
2891+
typeNameIdOfUnit = buildTypeMapIndex("unit");
28642892
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
2893+
typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
28652894

28662895
for (const crate in rawSearchIndex) {
28672896
if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {

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

+2-11
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,14 @@ const PARSED = [
9898
userQuery: "a<::a>",
9999
error: "Unexpected `::`: paths cannot start with `::`",
100100
},
101-
{
102-
query: "((a))",
103-
elems: [],
104-
foundElems: 0,
105-
original: "((a))",
106-
returned: [],
107-
userQuery: "((a))",
108-
error: "Unexpected `(`",
109-
},
110101
{
111102
query: "(p -> p",
112103
elems: [],
113104
foundElems: 0,
114105
original: "(p -> p",
115106
returned: [],
116107
userQuery: "(p -> p",
117-
error: "Unexpected `(`",
108+
error: "Unexpected `-` after `(`",
118109
},
119110
{
120111
query: "::a::b",
@@ -177,7 +168,7 @@ const PARSED = [
177168
original: "a (b:",
178169
returned: [],
179170
userQuery: "a (b:",
180-
error: "Unexpected `(`",
171+
error: "Expected `,`, `:` or `->`, found `(`",
181172
},
182173
{
183174
query: "_:",

0 commit comments

Comments
 (0)