rustdoc-search: search never type with !

This feature extends rustdoc to support the syntax that most users will
naturally attempt to use to search for diverging functions.
Part of #60485

It's already possible to do this search with `primitive:never`, but
that's not what the Rust language itself uses, so nobody will try it if
they aren't told or helped along.
This commit is contained in:
Michael Howell 2023-06-12 14:56:54 -07:00
parent df77afbcaf
commit db277f5284
7 changed files with 200 additions and 24 deletions

View File

@ -386,6 +386,35 @@ function initSearch(rawSearchIndex) {
if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) { if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
throw ["You cannot have more than one element if you use quotes"]; throw ["You cannot have more than one element if you use quotes"];
} }
const typeFilter = parserState.typeFilter;
parserState.typeFilter = null;
if (name === "!") {
if (typeFilter !== null && typeFilter !== "primitive") {
throw [
"Invalid search type: primitive never type ",
"!",
" and ",
typeFilter,
" both specified",
];
}
if (generics.length !== 0) {
throw [
"Never type ",
"!",
" does not accept generic parameters",
];
}
return {
name: "never",
id: -1,
fullPath: ["never"],
pathWithoutLast: [],
pathLast: "never",
generics: [],
typeFilter: "primitive",
};
}
const pathSegments = name.split("::"); const pathSegments = name.split("::");
if (pathSegments.length > 1) { if (pathSegments.length > 1) {
for (let i = 0, len = pathSegments.length; i < len; ++i) { for (let i = 0, len = pathSegments.length; i < len; ++i) {
@ -399,6 +428,13 @@ function initSearch(rawSearchIndex) {
} }
throw ["Unexpected ", "::::"]; throw ["Unexpected ", "::::"];
} }
if (pathSegment === "!") {
pathSegments[i] = "never";
if (i !== 0) {
throw ["Never type ", "!", " is not associated item"];
}
}
} }
} }
// In case we only have something like `<p>`, there is no name. // In case we only have something like `<p>`, there is no name.
@ -409,8 +445,6 @@ function initSearch(rawSearchIndex) {
if (isInGenerics) { if (isInGenerics) {
parserState.genericsElems += 1; parserState.genericsElems += 1;
} }
const typeFilter = parserState.typeFilter;
parserState.typeFilter = null;
return { return {
name: name, name: name,
id: -1, id: -1,
@ -459,10 +493,11 @@ function initSearch(rawSearchIndex) {
break; break;
} }
if (foundExclamation !== -1) { if (foundExclamation !== -1) {
if (start <= (end - 2)) { if (foundExclamation !== start &&
isIdentCharacter(parserState.userQuery[foundExclamation - 1])
) {
throw ["Cannot have associated items in macros"]; throw ["Cannot have associated items in macros"];
} else { } else {
// if start == end - 1, we got the never type
// while the never type has no associated macros, we still // while the never type has no associated macros, we still
// can parse a path like that // can parse a path like that
foundExclamation = -1; foundExclamation = -1;
@ -478,7 +513,10 @@ function initSearch(rawSearchIndex) {
end = parserState.pos; end = parserState.pos;
} }
// if start == end - 1, we got the never type // if start == end - 1, we got the never type
if (foundExclamation !== -1 && start <= (end - 2)) { if (foundExclamation !== -1 &&
foundExclamation !== start &&
isIdentCharacter(parserState.userQuery[foundExclamation - 1])
) {
if (parserState.typeFilter === null) { if (parserState.typeFilter === null) {
parserState.typeFilter = "macro"; parserState.typeFilter = "macro";
} else if (parserState.typeFilter !== "macro") { } else if (parserState.typeFilter !== "macro") {

View File

@ -1,6 +1,14 @@
const EXPECTED = { const EXPECTED = [
'query': '!', {
'others': [ 'query': '!',
{ 'path': 'std', 'name': 'never' }, 'others': [
], { 'path': 'std', 'name': 'never' },
}; ],
},
{
'query': '!::clone',
'others': [
{ 'path': 'std::never', 'name': 'clone' },
],
},
];

View File

@ -359,6 +359,15 @@ const PARSED = [
userQuery: "mod:a!", userQuery: "mod:a!",
error: 'Invalid search type: macro `!` and `mod` both specified', error: 'Invalid search type: macro `!` and `mod` both specified',
}, },
{
query: "mod:!",
elems: [],
foundElems: 0,
original: "mod:!",
returned: [],
userQuery: "mod:!",
error: 'Invalid search type: primitive never type `!` and `mod` both specified',
},
{ {
query: "a!::a", query: "a!::a",
elems: [], elems: [],

View File

@ -8,11 +8,12 @@ const PARSED = [
pathLast: "r", pathLast: "r",
generics: [ generics: [
{ {
name: "!", name: "never",
fullPath: ["!"], fullPath: ["never"],
pathWithoutLast: [], pathWithoutLast: [],
pathLast: "!", pathLast: "never",
generics: [], generics: [],
typeFilter: 15,
}, },
], ],
typeFilter: -1, typeFilter: -1,
@ -26,12 +27,12 @@ const PARSED = [
{ {
query: "!", query: "!",
elems: [{ elems: [{
name: "!", name: "never",
fullPath: ["!"], fullPath: ["never"],
pathWithoutLast: [], pathWithoutLast: [],
pathLast: "!", pathLast: "never",
generics: [], generics: [],
typeFilter: -1, typeFilter: 15,
}], }],
foundElems: 1, foundElems: 1,
original: "!", original: "!",
@ -64,12 +65,21 @@ const PARSED = [
userQuery: "a!::b", userQuery: "a!::b",
error: "Cannot have associated items in macros", error: "Cannot have associated items in macros",
}, },
{
query: "!<T>",
elems: [],
foundElems: 0,
original: "!<T>",
returned: [],
userQuery: "!<t>",
error: "Never type `!` does not accept generic parameters",
},
{ {
query: "!::b", query: "!::b",
elems: [{ elems: [{
name: "!::b", name: "!::b",
fullPath: ["!", "b"], fullPath: ["never", "b"],
pathWithoutLast: ["!"], pathWithoutLast: ["never"],
pathLast: "b", pathLast: "b",
generics: [], generics: [],
typeFilter: -1, typeFilter: -1,
@ -80,6 +90,58 @@ const PARSED = [
userQuery: "!::b", userQuery: "!::b",
error: null, error: null,
}, },
{
query: "b::!",
elems: [],
foundElems: 0,
original: "b::!",
returned: [],
userQuery: "b::!",
error: "Never type `!` is not associated item",
},
{
query: "!::!",
elems: [],
foundElems: 0,
original: "!::!",
returned: [],
userQuery: "!::!",
error: "Never type `!` is not associated item",
},
{
query: "b::!::c",
elems: [],
foundElems: 0,
original: "b::!::c",
returned: [],
userQuery: "b::!::c",
error: "Never type `!` is not associated item",
},
{
query: "!::b<T>",
elems: [{
name: "!::b",
fullPath: ["never", "b"],
pathWithoutLast: ["never"],
pathLast: "b",
generics: [
{
name: "t",
fullPath: ["t"],
pathWithoutLast: [],
pathLast: "t",
generics: [],
typeFilter: -1,
}
],
typeFilter: -1,
}],
foundElems: 1,
original: "!::b<T>",
returned: [],
userQuery: "!::b<t>",
error: null,
},
{ {
query: "a!::b!", query: "a!::b!",
elems: [], elems: [],

View File

@ -84,12 +84,12 @@ const PARSED = [
foundElems: 1, foundElems: 1,
original: "-> !", original: "-> !",
returned: [{ returned: [{
name: "!", name: "never",
fullPath: ["!"], fullPath: ["never"],
pathWithoutLast: [], pathWithoutLast: [],
pathLast: "!", pathLast: "never",
generics: [], generics: [],
typeFilter: -1, typeFilter: 15,
}], }],
userQuery: "-> !", userQuery: "-> !",
error: null, error: null,

View File

@ -0,0 +1,46 @@
// exact-check
const EXPECTED = [
{
'query': '-> !',
'others': [
{ 'path': 'never_search', 'name': 'loops' },
],
},
{
'query': '-> never',
'others': [
{ 'path': 'never_search', 'name': 'loops' },
{ 'path': 'never_search', 'name': 'returns' },
],
},
{
'query': '!',
'in_args': [
{ 'path': 'never_search', 'name': 'impossible' },
{ 'path': 'never_search', 'name': 'box_impossible' },
],
},
{
'query': 'never',
'in_args': [
{ 'path': 'never_search', 'name': 'impossible' },
{ 'path': 'never_search', 'name': 'uninteresting' },
{ 'path': 'never_search', 'name': 'box_impossible' },
{ 'path': 'never_search', 'name': 'box_uninteresting' },
],
},
{
'query': 'box<!>',
'in_args': [
{ 'path': 'never_search', 'name': 'box_impossible' },
],
},
{
'query': 'box<never>',
'in_args': [
{ 'path': 'never_search', 'name': 'box_impossible' },
{ 'path': 'never_search', 'name': 'box_uninteresting' },
],
},
];

View File

@ -0,0 +1,13 @@
#![feature(never_type)]
#[allow(nonstandard_style)]
pub struct never;
pub fn loops() -> ! { loop {} }
pub fn returns() -> never { never }
pub fn impossible(x: !) { match x {} }
pub fn uninteresting(x: never) { match x { never => {} } }
pub fn box_impossible(x: Box<!>) { match *x {} }
pub fn box_uninteresting(x: Box<never>) { match *x { never => {} } }