diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md index d17e41bcde7de..e2def14b357ea 100644 --- a/src/doc/rustdoc/src/read-documentation/search.md +++ b/src/doc/rustdoc/src/read-documentation/search.md @@ -153,16 +153,26 @@ will match these queries: But it *does not* match `Result` or `Result>`. +To search for a function that accepts a function as a parameter, +like `Iterator::all`, wrap the nested signature in parenthesis, +as in [`Iterator, (T -> bool) -> bool`][iterator-all]. +You can also search for a specific closure trait, +such as `Iterator, (FnMut(T) -> bool) -> bool`, +but you need to know which one you want. + +[iterator-all]: ../../std/vec/struct.Vec.html?search=Iterator%2C+(T+->+bool)+->+bool&filter-crate=std + ### Primitives with Special Syntax -| Shorthand | Explicit names | -| --------- | ------------------------------------------------ | -| `[]` | `primitive:slice` and/or `primitive:array` | -| `[T]` | `primitive:slice` and/or `primitive:array` | -| `()` | `primitive:unit` and/or `primitive:tuple` | -| `(T)` | `T` | -| `(T,)` | `primitive:tuple` | -| `!` | `primitive:never` | +| Shorthand | Explicit names | +| ---------------- | ------------------------------------------------- | +| `[]` | `primitive:slice` and/or `primitive:array` | +| `[T]` | `primitive:slice` and/or `primitive:array` | +| `()` | `primitive:unit` and/or `primitive:tuple` | +| `(T)` | `T` | +| `(T,)` | `primitive:tuple` | +| `!` | `primitive:never` | +| `(T, U -> V, W)` | `fn(T, U) -> (V, W)`, `Fn`, `FnMut`, and `FnOnce` | 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 @@ -182,6 +192,10 @@ results for types that match tuples, even though it also matches the type on its own. That is, `(u32)` matches `(u32,)` for the exact same reason that it also matches `Result`. +The `->` operator has lower precedence than comma. If it's not wrapped +in brackets, it delimits the return value for the function being searched for. +To search for functions that take functions as parameters, use parenthesis. + ### Limitations and quirks of type-based search Type-based search is still a buggy, experimental, work-in-progress feature. @@ -220,9 +234,6 @@ Most of these limitations should be addressed in future version of Rustdoc. * Searching for lifetimes is not supported. - * It's impossible to search for closures based on their parameters or - return values. - * It's impossible to search based on the length of an array. ## Item filtering @@ -239,19 +250,21 @@ Item filters can be used in both name-based and type signature-based searches. ```text ident = *(ALPHA / DIGIT / "_") -path = ident *(DOUBLE-COLON ident) [!] +path = ident *(DOUBLE-COLON ident) [BANG] 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 / [!]) +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) +nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ] generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep) -generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep) +normal-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep) CLOSE-ANGLE-BRACKET +fn-like-generics = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN [ RETURN-ARROW arg ] +generics = normal-generics / fn-like-generics return-args = RETURN-ARROW *(type-sep) nonempty-arg-list exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ] -type-search = [ nonempty-arg-list ] [ return-args ] +type-search = [ nonempty-arg-list ] query = *WS (exact-search / type-search) *WS @@ -296,6 +309,7 @@ QUOTE = %x22 COMMA = "," RETURN-ARROW = "->" EQUAL = "=" +BANG = "!" ALPHA = %x41-5A / %x61-7A ; A-Z / a-z DIGIT = %x30-39 diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 6c285ba6f5102..73ac5608479b1 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1,3 +1,4 @@ +// ignore-tidy-filelength /* global addClass, getNakedUrl, getSettingValue */ /* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */ @@ -578,7 +579,10 @@ function initSearch(rawSearchIndex) { // Syntactically, bindings are parsed as generics, // but the query engine treats them differently. if (gen.bindingName !== null) { - bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]); + if (gen.name !== null) { + gen.bindingName.generics.unshift(gen); + } + bindings.set(gen.bindingName.name, gen.bindingName.generics); return false; } return true; @@ -678,6 +682,38 @@ function initSearch(rawSearchIndex) { return end; } + function getFilteredNextElem(query, parserState, elems, isInGenerics) { + const start = parserState.pos; + if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) { + throw ["Expected type filter before ", ":"]; + } + getNextElem(query, parserState, elems, isInGenerics); + if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) { + if (parserState.typeFilter !== null) { + throw [ + "Unexpected ", + ":", + " (expected path after type filter ", + parserState.typeFilter + ":", + ")", + ]; + } + if (elems.length === 0) { + throw ["Expected type filter before ", ":"]; + } else if (query.literalSearch) { + throw ["Cannot use quotes on type filter"]; + } + // The type filter doesn't count as an element since it's a modifier. + const typeFilterElem = elems.pop(); + checkExtraTypeFilterCharacters(start, parserState); + parserState.typeFilter = typeFilterElem.name; + parserState.pos += 1; + parserState.totalElems -= 1; + query.literalSearch = false; + getNextElem(query, parserState, elems, isInGenerics); + } + } + /** * @param {ParsedQuery} query * @param {ParserState} parserState @@ -752,6 +788,32 @@ function initSearch(rawSearchIndex) { } parserState.pos += 1; getItemsBefore(query, parserState, generics, ">"); + } else if (parserState.pos < parserState.length && + parserState.userQuery[parserState.pos] === "(" + ) { + if (start >= end) { + throw ["Found generics without a path"]; + } + if (parserState.isInBinding) { + throw ["Unexpected ", "(", " after ", "="]; + } + parserState.pos += 1; + const typeFilter = parserState.typeFilter; + parserState.typeFilter = null; + getItemsBefore(query, parserState, generics, ")"); + skipWhitespace(parserState); + if (isReturnArrow(parserState)) { + parserState.pos += 2; + skipWhitespace(parserState); + getFilteredNextElem(query, parserState, generics, isInGenerics); + generics[generics.length - 1].bindingName = makePrimitiveElement("output"); + } else { + generics.push(makePrimitiveElement(null, { + bindingName: makePrimitiveElement("output"), + typeFilter: null, + })); + } + parserState.typeFilter = typeFilter; } if (isStringElem) { skipWhitespace(parserState); @@ -811,7 +873,6 @@ function initSearch(rawSearchIndex) { 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. const oldTypeFilter = parserState.typeFilter; @@ -874,24 +935,6 @@ function initSearch(rawSearchIndex) { continue; } else if (c === ":" && isPathStart(parserState)) { throw ["Unexpected ", "::", ": paths cannot start with ", "::"]; - } else if (c === ":") { - if (parserState.typeFilter !== null) { - throw ["Unexpected ", ":"]; - } - if (elems.length === 0) { - throw ["Expected type filter before ", ":"]; - } else if (query.literalSearch) { - throw ["Cannot use quotes on type filter"]; - } - // The type filter doesn't count as an element since it's a modifier. - const typeFilterElem = elems.pop(); - checkExtraTypeFilterCharacters(start, parserState); - parserState.typeFilter = typeFilterElem.name; - parserState.pos += 1; - parserState.totalElems -= 1; - query.literalSearch = false; - foundStopChar = true; - continue; } else if (isEndCharacter(c)) { throw ["Unexpected ", c, " after ", extra]; } @@ -926,8 +969,7 @@ function initSearch(rawSearchIndex) { ]; } const posBefore = parserState.pos; - start = parserState.pos; - getNextElem(query, parserState, elems, endChar !== ""); + getFilteredNextElem(query, parserState, elems, endChar !== ""); if (endChar !== "" && parserState.pos >= parserState.length) { throw ["Unclosed ", extra]; } @@ -1004,7 +1046,6 @@ function initSearch(rawSearchIndex) { */ function parseInput(query, parserState) { let foundStopChar = true; - let start = parserState.pos; while (parserState.pos < parserState.length) { const c = parserState.userQuery[parserState.pos]; @@ -1022,29 +1063,6 @@ function initSearch(rawSearchIndex) { throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]]; } throw ["Unexpected ", c]; - } else if (c === ":" && !isPathStart(parserState)) { - if (parserState.typeFilter !== null) { - throw [ - "Unexpected ", - ":", - " (expected path after type filter ", - parserState.typeFilter + ":", - ")", - ]; - } else if (query.elems.length === 0) { - throw ["Expected type filter before ", ":"]; - } else if (query.literalSearch) { - throw ["Cannot use quotes on type filter"]; - } - // The type filter doesn't count as an element since it's a modifier. - const typeFilterElem = query.elems.pop(); - checkExtraTypeFilterCharacters(start, parserState); - parserState.typeFilter = typeFilterElem.name; - parserState.pos += 1; - parserState.totalElems -= 1; - query.literalSearch = false; - foundStopChar = true; - continue; } else if (c === " ") { skipWhitespace(parserState); continue; @@ -1080,8 +1098,7 @@ function initSearch(rawSearchIndex) { ]; } const before = query.elems.length; - start = parserState.pos; - getNextElem(query, parserState, query.elems, false); + getFilteredNextElem(query, parserState, query.elems, false); if (query.elems.length === before) { // Nothing was added, weird... Let's increase the position to not remain stuck. parserState.pos += 1; diff --git a/tests/rustdoc-js-std/parser-errors.js b/tests/rustdoc-js-std/parser-errors.js index 8efb81841d47f..ffd169812b631 100644 --- a/tests/rustdoc-js-std/parser-errors.js +++ b/tests/rustdoc-js-std/parser-errors.js @@ -195,7 +195,7 @@ const PARSED = [ original: "a (b:", returned: [], userQuery: "a (b:", - error: "Expected `,`, `:` or `->`, found `(`", + error: "Unclosed `(`", }, { query: "_:", @@ -357,7 +357,16 @@ const PARSED = [ original: "a,:", returned: [], userQuery: "a,:", - error: 'Unexpected `,` in type filter (before `:`)', + error: 'Expected type filter before `:`', + }, + { + query: "a!:", + elems: [], + foundElems: 0, + original: "a!:", + returned: [], + userQuery: "a!:", + error: 'Unexpected `!` in type filter (before `:`)', }, { query: " a<> :", @@ -366,7 +375,7 @@ const PARSED = [ original: "a<> :", returned: [], userQuery: "a<> :", - error: 'Unexpected `<` in type filter (before `:`)', + error: 'Expected `,`, `:` or `->` after `>`, found `:`', }, { query: "mod : :", diff --git a/tests/rustdoc-js-std/parser-hof.js b/tests/rustdoc-js-std/parser-hof.js index 331c516e047a9..0b99c45b7a922 100644 --- a/tests/rustdoc-js-std/parser-hof.js +++ b/tests/rustdoc-js-std/parser-hof.js @@ -1,4 +1,5 @@ const PARSED = [ + // ML-style HOF { query: "(-> F

)", elems: [{ @@ -373,4 +374,339 @@ const PARSED = [ userQuery: "x, trait:(aaaaa, b -> a)", error: null, }, + // Rust-style HOF + { + query: "Fn () -> F

", + elems: [{ + name: "fn", + fullPath: ["fn"], + pathWithoutLast: [], + pathLast: "fn", + generics: [], + bindings: [ + [ + "output", + [{ + name: "f", + fullPath: ["f"], + pathWithoutLast: [], + pathLast: "f", + generics: [ + { + name: "p", + fullPath: ["p"], + pathWithoutLast: [], + pathLast: "p", + generics: [], + }, + ], + typeFilter: -1, + }], + ], + ], + typeFilter: -1, + }], + foundElems: 1, + original: "Fn () -> F

", + returned: [], + userQuery: "fn () -> f

", + error: null, + }, + { + query: "FnMut() -> P", + elems: [{ + name: "fnmut", + fullPath: ["fnmut"], + pathWithoutLast: [], + pathLast: "fnmut", + generics: [], + bindings: [ + [ + "output", + [{ + name: "p", + fullPath: ["p"], + pathWithoutLast: [], + pathLast: "p", + generics: [], + typeFilter: -1, + }], + ], + ], + typeFilter: -1, + }], + foundElems: 1, + original: "FnMut() -> P", + returned: [], + userQuery: "fnmut() -> p", + error: null, + }, + { + query: "(FnMut() -> P)", + elems: [{ + name: "fnmut", + fullPath: ["fnmut"], + pathWithoutLast: [], + pathLast: "fnmut", + generics: [], + bindings: [ + [ + "output", + [{ + name: "p", + fullPath: ["p"], + pathWithoutLast: [], + pathLast: "p", + generics: [], + typeFilter: -1, + }], + ], + ], + typeFilter: -1, + }], + foundElems: 1, + original: "(FnMut() -> P)", + returned: [], + userQuery: "(fnmut() -> p)", + error: null, + }, + { + query: "Fn(F

)", + elems: [{ + name: "fn", + fullPath: ["fn"], + pathWithoutLast: [], + pathLast: "fn", + generics: [{ + name: "f", + fullPath: ["f"], + pathWithoutLast: [], + pathLast: "f", + generics: [ + { + name: "p", + fullPath: ["p"], + pathWithoutLast: [], + pathLast: "p", + generics: [], + }, + ], + typeFilter: -1, + }], + bindings: [ + [ + "output", + [], + ], + ], + typeFilter: -1, + }], + foundElems: 1, + original: "Fn(F

)", + returned: [], + userQuery: "fn(f

)", + error: null, + }, + { + query: "primitive:fnonce(aaaaa, b) -> a", + elems: [{ + name: "fnonce", + fullPath: ["fnonce"], + pathWithoutLast: [], + pathLast: "fnonce", + generics: [ + { + name: "aaaaa", + fullPath: ["aaaaa"], + pathWithoutLast: [], + pathLast: "aaaaa", + generics: [], + typeFilter: -1, + }, + { + name: "b", + fullPath: ["b"], + pathWithoutLast: [], + pathLast: "b", + generics: [], + typeFilter: -1, + }, + ], + bindings: [ + [ + "output", + [{ + name: "a", + fullPath: ["a"], + pathWithoutLast: [], + pathLast: "a", + generics: [], + typeFilter: -1, + }], + ], + ], + typeFilter: 1, + }], + foundElems: 1, + original: "primitive:fnonce(aaaaa, b) -> a", + returned: [], + userQuery: "primitive:fnonce(aaaaa, b) -> a", + error: null, + }, + { + query: "primitive:fnonce(aaaaa, keyword:b) -> trait:a", + elems: [{ + name: "fnonce", + fullPath: ["fnonce"], + pathWithoutLast: [], + pathLast: "fnonce", + generics: [ + { + name: "aaaaa", + fullPath: ["aaaaa"], + pathWithoutLast: [], + pathLast: "aaaaa", + generics: [], + typeFilter: -1, + }, + { + name: "b", + fullPath: ["b"], + pathWithoutLast: [], + pathLast: "b", + generics: [], + typeFilter: 0, + }, + ], + bindings: [ + [ + "output", + [{ + name: "a", + fullPath: ["a"], + pathWithoutLast: [], + pathLast: "a", + generics: [], + typeFilter: 10, + }], + ], + ], + typeFilter: 1, + }], + foundElems: 1, + original: "primitive:fnonce(aaaaa, keyword:b) -> trait:a", + returned: [], + userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a", + error: null, + }, + { + query: "x, trait:fn(aaaaa, b -> a)", + elems: [ + { + name: "x", + fullPath: ["x"], + pathWithoutLast: [], + pathLast: "x", + generics: [], + typeFilter: -1, + }, + { + name: "fn", + fullPath: ["fn"], + pathWithoutLast: [], + pathLast: "fn", + generics: [ + { + name: "->", + fullPath: ["->"], + pathWithoutLast: [], + pathLast: "->", + generics: [ + { + name: "aaaaa", + fullPath: ["aaaaa"], + pathWithoutLast: [], + pathLast: "aaaaa", + generics: [], + typeFilter: -1, + }, + { + name: "b", + fullPath: ["b"], + pathWithoutLast: [], + pathLast: "b", + generics: [], + typeFilter: -1, + }, + ], + bindings: [ + [ + "output", + [{ + name: "a", + fullPath: ["a"], + pathWithoutLast: [], + pathLast: "a", + generics: [], + typeFilter: -1, + }], + ], + ], + typeFilter: -1, + }, + ], + bindings: [ + [ + "output", + [], + ] + ], + typeFilter: 10, + } + ], + foundElems: 2, + original: "x, trait:fn(aaaaa, b -> a)", + returned: [], + userQuery: "x, trait:fn(aaaaa, b -> a)", + error: null, + }, + { + query: 'a,b(c)', + elems: [ + { + name: "a", + fullPath: ["a"], + pathWithoutLast: [], + pathLast: "a", + generics: [], + typeFilter: -1, + }, + { + name: "b", + fullPath: ["b"], + pathWithoutLast: [], + pathLast: "b", + generics: [{ + name: "c", + fullPath: ["c"], + pathWithoutLast: [], + pathLast: "c", + generics: [], + typeFilter: -1, + }], + bindings: [ + [ + "output", + [], + ] + ], + typeFilter: -1, + } + ], + foundElems: 2, + original: "a,b(c)", + returned: [], + userQuery: "a,b(c)", + error: null, + }, ]; diff --git a/tests/rustdoc-js-std/parser-weird-queries.js b/tests/rustdoc-js-std/parser-weird-queries.js index 26b8c32d68052..499b82a346948 100644 --- a/tests/rustdoc-js-std/parser-weird-queries.js +++ b/tests/rustdoc-js-std/parser-weird-queries.js @@ -37,15 +37,6 @@ const PARSED = [ userQuery: "a b", error: null, }, - { - query: 'a,b(c)', - elems: [], - foundElems: 0, - original: "a,b(c)", - returned: [], - userQuery: "a,b(c)", - error: "Expected `,`, `:` or `->`, found `(`", - }, { query: 'aaa,a', elems: [ diff --git a/tests/rustdoc-js/hof.js b/tests/rustdoc-js/hof.js index 1f5d2bfb6666c..5e6c9d83c7c7f 100644 --- a/tests/rustdoc-js/hof.js +++ b/tests/rustdoc-js/hof.js @@ -1,6 +1,12 @@ // exact-check const EXPECTED = [ + // not a HOF query + { + 'query': 'u32 -> !', + 'others': [], + }, + // ML-style higher-order function notation { 'query': 'bool, (u32 -> !) -> ()', @@ -53,11 +59,6 @@ const EXPECTED = [ {"path": "hof", "name": "fn_once"}, ], }, - { - 'query': 'u32 -> !', - // not a HOF query - 'others': [], - }, { 'query': '(str, str -> i8) -> ()', 'others': [ @@ -91,4 +92,85 @@ const EXPECTED = [ // params and return are not the same 'others': [], }, + + // Rust-style higher-order function notation + { + 'query': 'bool, fn(u32) -> ! -> ()', + 'others': [ + {"path": "hof", "name": "fn_ptr"}, + ], + }, + { + 'query': 'u8, fnonce(u32) -> ! -> ()', + 'others': [ + {"path": "hof", "name": "fn_once"}, + ], + }, + { + 'query': 'u8, fn(u32) -> ! -> ()', + // fnonce != fn + 'others': [], + }, + { + 'query': 'i8, fnmut(u32) -> ! -> ()', + 'others': [ + {"path": "hof", "name": "fn_mut"}, + ], + }, + { + 'query': 'i8, fn(u32) -> ! -> ()', + // fnmut != fn + 'others': [], + }, + { + 'query': 'char, fn(u32) -> ! -> ()', + 'others': [ + {"path": "hof", "name": "fn_"}, + ], + }, + { + 'query': 'char, fnmut(u32) -> ! -> ()', + // fn != fnmut + 'others': [], + }, + { + 'query': 'fn(first) -> ! -> ()', + 'others': [ + {"path": "hof", "name": "fn_ptr"}, + ], + }, + { + 'query': 'fnonce(second) -> ! -> ()', + 'others': [ + {"path": "hof", "name": "fn_once"}, + ], + }, + { + 'query': 'fnmut(third) -> ! -> ()', + 'others': [ + {"path": "hof", "name": "fn_mut"}, + ], + }, + { + 'query': 'fn(u32) -> ! -> ()', + 'others': [ + // fn matches primitive:fn and trait:Fn + {"path": "hof", "name": "fn_"}, + {"path": "hof", "name": "fn_ptr"}, + ], + }, + { + 'query': 'trait:fn(u32) -> ! -> ()', + 'others': [ + // fn matches primitive:fn and trait:Fn + {"path": "hof", "name": "fn_"}, + ], + }, + { + 'query': 'primitive:fn(u32) -> ! -> ()', + 'others': [ + // fn matches primitive:fn and trait:Fn + {"path": "hof", "name": "fn_ptr"}, + ], + }, ];