Skip to content

Commit

Permalink
rustdoc: search for tuples and unit by type with ()
Browse files Browse the repository at this point in the history
  • Loading branch information
notriddle committed Nov 22, 2023
1 parent 06d1afe commit de1cb5c
Show file tree
Hide file tree
Showing 8 changed files with 561 additions and 50 deletions.
67 changes: 56 additions & 11 deletions src/doc/rustdoc/src/read-documentation/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 150,55 @@ will match these queries:

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

Function signature searches also support arrays and slices. The explicit name
`primitive:slice<u8>` and `primitive:array<u8>` can be used to match a slice
or array of bytes, while square brackets `[u8]` will match either one. Empty
square brackets, `[]`, will match any slice or array regardless of what
it contains, while a slice with a type parameter, like `[T]`, will only match
functions that actually operate on generic slices.
### Primitives with Special Syntax

<table>
<thead>
<tr>
<th>Shorthand</th>
<th>Explicit names</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[]</code></td>
<td><code>primitive:slice</code> and/or <code>primitive:array</code></td>
</tr>
<tr>
<td><code>[T]</code></td>
<td><code>primitive:slice&lt;T&gt;</code> and/or <code>primitive:array&lt;T&gt;</code></td>
</tr>
<tr>
<td><code>()</code></td>
<td><code>primitive:unit</code> and/or <code>primitive:tuple</code></td>
</tr>
<tr>
<td><code>(T)</code></td>
<td><code>T</code></td>
</tr>
<tr>
<td><code>(T,)</code></td>
<td><code>primitive:tuple&lt;T&gt;</code></td>
</tr>
<tr>
<td><code>!</code></td>
<td><code>primitive:never</code></td>
</tr>
</tbody>
</table>

When searching for `[]`, Rustdoc will return search results with either slices
or arrays. If you know which one you want, you can force it to return results
for `primitive:slice` or `primitive:array` using the explicit name syntax.
Empty square brackets, `[]`, will match any slice or array regardless of what
it contains, or an item type can be provided, such as `[u8]` or `[T]`, to
explicitly find functions that operate on byte slices or generic slices,
respectively.

A single type expression wrapped in parens is the same as that type expression,
since parens act as the grouping operator. If they're empty, though, they will
match both `unit` and `tuple`, and if there's more than one type (or a trailing
or leading comma) it is the same as `primitive:tuple<...>`.

### Limitations and quirks of type-based search

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

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

* Searching for lifetimes is not supported.

Expand All @@ -216,8 258,9 @@ Item filters can be used in both name-based and type signature-based searches.
```text
ident = *(ALPHA / DIGIT / "_")
path = ident *(DOUBLE-COLON ident) [!]
slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
arg = [type-filter *WS COLON *WS] (path [generics] / slice / [!])
slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / [!])
type-sep = COMMA/WS *(COMMA/WS)
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
Expand Down Expand Up @@ -263,6 306,8 @@ OPEN-ANGLE-BRACKET = "<"
CLOSE-ANGLE-BRACKET = ">"
OPEN-SQUARE-BRACKET = "["
CLOSE-SQUARE-BRACKET = "]"
OPEN-PAREN = "("
CLOSE-PAREN = ")"
COLON = ":"
DOUBLE-COLON = "::"
QUOTE = %x22
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/html/render/search_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 581,7 @@ fn get_index_type_id(
// 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(ref n) if n.is_empty() => Some(RenderTypeId::Primitive(clean::PrimitiveType::Unit)),
clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)),
clean::QPath(ref data) => {
if data.self_type.is_self_type()
Expand Down
83 changes: 56 additions & 27 deletions src/librustdoc/html/static/js/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 260,18 @@ function initSearch(rawSearchIndex) {
* Special type name IDs for searching by both array and slice (`[]` syntax).
*/
let typeNameIdOfArrayOrSlice;
/**
* Special type name IDs for searching by tuple.
*/
let typeNameIdOfTuple;
/**
* Special type name IDs for searching by unit.
*/
let typeNameIdOfUnit;
/**
* Special type name IDs for searching by both tuple and unit (`()` syntax).
*/
let typeNameIdOfTupleOrUnit;

/**
* Add an item to the type Name->ID map, or, if one already exists, use it.
Expand Down Expand Up @@ -296,17 308,13 @@ function initSearch(rawSearchIndex) {
}

function isEndCharacter(c) {
return "=,>-]".indexOf(c) !== -1;
return "=,>-])".indexOf(c) !== -1;
}

function isStopCharacter(c) {
return isEndCharacter(c);
}

function isErrorCharacter(c) {
return "()".indexOf(c) !== -1;
}

function itemTypeFromName(typename) {
const index = itemTypes.findIndex(i => i === typename);
if (index < 0) {
Expand Down Expand Up @@ -592,8 600,6 @@ function initSearch(rawSearchIndex) {
throw ["Unexpected ", "!", ": it can only be at the end of an ident"];
}
foundExclamation = parserState.pos;
} else if (isErrorCharacter(c)) {
throw ["Unexpected ", c];
} else if (isPathSeparator(c)) {
if (c === ":") {
if (!isPathStart(parserState)) {
Expand Down Expand Up @@ -623,6 629,7 @@ function initSearch(rawSearchIndex) {
}
} else if (
c === "[" ||
c === "(" ||
c === "=" ||
isStopCharacter(c) ||
isSpecialStartCharacter(c) ||
Expand Down Expand Up @@ -669,42 676,49 @@ function initSearch(rawSearchIndex) {
skipWhitespace(parserState);
let start = parserState.pos;
let end;
if (parserState.userQuery[parserState.pos] === "[") {
if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {
const endChar = parserState.userQuery[parserState.pos] === "[" ? "]" : ")";
const name = parserState.userQuery[parserState.pos] === "[" ? "[]" : "()";
const friendlyName = parserState.userQuery[parserState.pos] === "[" ? "slice" : "tuple";
parserState.pos = 1;
getItemsBefore(query, parserState, generics, "]");
const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);
const typeFilter = parserState.typeFilter;
const isInBinding = parserState.isInBinding;
if (typeFilter !== null && typeFilter !== "primitive") {
throw [
"Invalid search type: primitive ",
"[]",
name,
" and ",
typeFilter,
" both specified",
];
}
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 ", "[]"];
throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];
}
}
elems.push({
name: "[]",
id: null,
fullPath: ["[]"],
pathWithoutLast: [],
pathLast: "[]",
generics,
typeFilter: "primitive",
bindingName: isInBinding,
bindings: new Map(),
});
if (name === "()" && !foundSeparator && generics.length === 1 && typeFilter === null) {
elems.push(generics[0]);
} else {
parserState.totalElems = 1;
if (isInGenerics) {
parserState.genericsElems = 1;
}
elems.push({
name: name,
id: null,
fullPath: [name],
pathWithoutLast: [],
pathLast: name,
generics,
bindings: new Map(),
typeFilter: "primitive",
bindingName: isInBinding,
});
}
} else {
const isStringElem = parserState.userQuery[start] === "\"";
// We handle the strings on their own mostly to make code easier to follow.
Expand Down Expand Up @@ -777,9 791,11 @@ function initSearch(rawSearchIndex) {
* @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
* @param {string} endChar - This function will stop when it'll encounter this
* character.
* @returns {{foundSeparator: bool}}
*/
function getItemsBefore(query, parserState, elems, endChar) {
let foundStopChar = true;
let foundSeparator = false;
let start = parserState.pos;

// If this is a generic, keep the outer item's type filter around.
Expand All @@ -793,6 809,8 @@ function initSearch(rawSearchIndex) {
extra = "<";
} else if (endChar === "]") {
extra = "[";
} else if (endChar === ")") {
extra = "(";
} else if (endChar === "") {
extra = "->";
} else {
Expand All @@ -809,6 827,7 @@ function initSearch(rawSearchIndex) {
} else if (isSeparatorCharacter(c)) {
parserState.pos = 1;
foundStopChar = true;
foundSeparator = true;
continue;
} else if (c === ":" && isPathStart(parserState)) {
throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
Expand Down Expand Up @@ -886,6 905,8 @@ function initSearch(rawSearchIndex) {

parserState.typeFilter = oldTypeFilter;
parserState.isInBinding = oldIsInBinding;

return { foundSeparator };
}

/**
Expand Down Expand Up @@ -1625,6 1646,11 @@ function initSearch(rawSearchIndex) {
) {
// [] matches primitive:array or primitive:slice
// if it matches, then we're fine, and this is an appropriate match candidate
} else if (queryElem.id === typeNameIdOfTupleOrUnit &&
(fnType.id === typeNameIdOfTuple || fnType.id === typeNameIdOfUnit)
) {
// () matches primitive:tuple or primitive:unit
// if it matches, then we're fine, and this is an appropriate match candidate
} else if (fnType.id !== queryElem.id || queryElem.id === null) {
return false;
}
Expand Down Expand Up @@ -1807,7 1833,7 @@ function initSearch(rawSearchIndex) {
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
elem.id !== typeNameIdOfArrayOrSlice && elem.id !== typeNameIdOfTupleOrUnit
) {
return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
}
Expand Down Expand Up @@ -2861,7 2887,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// that can be searched using `[]` syntax.
typeNameIdOfArray = buildTypeMapIndex("array");
typeNameIdOfSlice = buildTypeMapIndex("slice");
typeNameIdOfTuple = buildTypeMapIndex("tuple");
typeNameIdOfUnit = buildTypeMapIndex("unit");
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");

for (const crate in rawSearchIndex) {
if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
Expand Down
13 changes: 2 additions & 11 deletions tests/rustdoc-js-std/parser-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 98,14 @@ const PARSED = [
userQuery: "a<::a>",
error: "Unexpected `::`: paths cannot start with `::`",
},
{
query: "((a))",
elems: [],
foundElems: 0,
original: "((a))",
returned: [],
userQuery: "((a))",
error: "Unexpected `(`",
},
{
query: "(p -> p",
elems: [],
foundElems: 0,
original: "(p -> p",
returned: [],
userQuery: "(p -> p",
error: "Unexpected `(`",
error: "Unexpected `-` after `(`",
},
{
query: "::a::b",
Expand Down Expand Up @@ -177,7 168,7 @@ const PARSED = [
original: "a (b:",
returned: [],
userQuery: "a (b:",
error: "Unexpected `(`",
error: "Expected `,`, `:` or `->`, found `(`",
},
{
query: "_:",
Expand Down
Loading

0 comments on commit de1cb5c

Please sign in to comment.