Skip to content

Commit

Permalink
rustdoc: fix corner cases in unboxing and type parameters
Browse files Browse the repository at this point in the history
Turns out I actually *do* need to backtrack across a type param.
There could be more than one possible solution behind it, and
the solution that's chosen can affect matches outside the type
param, so all possible solutions for the type param need checked.
  • Loading branch information
notriddle committed Oct 4, 2023
1 parent b0a34a8 commit 3673c10
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 64 deletions.
136 changes: 72 additions & 64 deletions src/librustdoc/html/static/js/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -1389,29 +1389,35 @@ function initSearch(rawSearchIndex) {
* @return {boolean} - Returns true if a match, false otherwise.
*/
function checkGenerics(fnType, queryElem, whereClause, mgensInout) {
const simplifiedGenerics = unifyFunctionTypeCheckBindings(
const solutions = unifyFunctionTypeCheckBindings(
fnType,
queryElem,
whereClause,
mgensInout
);
if (!simplifiedGenerics) {
if (!solutions) {
return false;
}
return unifyFunctionTypes(
simplifiedGenerics,
queryElem.generics,
whereClause,
mgensInout,
mgens => {
if (mgensInout) {
for (const [fid, qid] of mgens.entries()) {
mgensInout.set(fid, qid);
const simplifiedGenerics = solutions.simplifiedGenerics;
for (const mgens of solutions.mgens) {
if (unifyFunctionTypes(
simplifiedGenerics,
queryElem.generics,
whereClause,
mgens,
mgens => {
if (mgensInout) {
for (const [fid, qid] of mgens.entries()) {
mgensInout.set(fid, qid);
}
}
return true;
}
)) {
return true;
}
);
}
return false;
}
/**
* This function checks if a list of search query `queryElems` can all be found in the
Expand Down Expand Up @@ -1545,33 +1551,36 @@ function initSearch(rawSearchIndex) {
for (j = i; j !== fl; ++j) {
const fnType = fnTypes[j];
if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
const mgensScratch = new Map(mgens);
const simplifiedGenerics = unifyFunctionTypeCheckBindings(
const solution = unifyFunctionTypeCheckBindings(
fnType,
queryElem,
whereClause,
mgensScratch
mgens
);
if (simplifiedGenerics) {
if (solution) {
if (!fnTypesScratch) {
fnTypesScratch = fnTypes.slice();
}
unifyFunctionTypes(
simplifiedGenerics,
queryElem.generics,
whereClause,
mgensScratch,
mgensScratch => {
matchCandidates.push({
fnTypesScratch,
mgensScratch,
queryElemsOffset: i,
fnTypesOffset: j,
unbox: false,
});
return false; // "reject" all candidates to gather all of them
}
);
const simplifiedGenerics = solution.simplifiedGenerics;
for (const solutionMgens of solution.mgens) {
unifyFunctionTypes(
simplifiedGenerics,
queryElem.generics,
whereClause,
solutionMgens,
mgensScratch => {
matchCandidates.push({
fnTypesScratch,
mgensScratch,
queryElemsOffset: i,
fnTypesOffset: j,
unbox: false,
});
// "reject" all candidates to gather all of them
return false;
}
);
}
}
}
if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
Expand Down Expand Up @@ -1726,41 +1735,44 @@ function initSearch(rawSearchIndex) {
* @param {FunctionType} fnType
* @param {QueryElement} queryElem
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgensInout - Map functions generics to query generics.
* Written on success.
* @returns {boolean|FunctionType[]}
* @param {Map<number,number>} mgensIn - Map functions generics to query generics.
* Never modified.
* @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
*/
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensInout) {
// Simplify generics now
let simplifiedGenerics = fnType.generics;
if (!simplifiedGenerics) {
simplifiedGenerics = [];
}
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
if (fnType.bindings.size < queryElem.bindings.size) {
return false;
}
let simplifiedGenerics = fnType.generics || [];
if (fnType.bindings.size > 0) {
const mgensResults = new Map(mgensInout);
let mgensSolutionSet = [mgensIn];
for (const [name, constraints] of queryElem.bindings.entries()) {
if (!fnType.bindings.has(name)) {
if (mgensSolutionSet.length === 0) {
return false;
}
// Since both items must have exactly one entry per name,
// we don't need to backtrack here, but do need to write mgens.
if (!unifyFunctionTypes(
fnType.bindings.get(name),
constraints,
whereClause,
mgensResults,
mgens => {
for (const [fid, qid] of mgens.entries()) {
mgensResults.set(fid, qid);
}
return true;
}
)) {
if (!fnType.bindings.has(name)) {
return false;
}
const fnTypeBindings = fnType.bindings.get(name);
mgensSolutionSet = mgensSolutionSet.flatMap(mgens => {
const newSolutions = [];
unifyFunctionTypes(
fnTypeBindings,
constraints,
whereClause,
mgens,
newMgens => {
newSolutions.push(newMgens);
// return `false` makes unifyFunctionTypes return the full set of
// possible solutions
return false;
}
);
return newSolutions;
});
}
if (mgensSolutionSet.length === 0) {
return false;
}
const binds = Array.from(fnType.bindings.entries()).flatMap(entry => {
const [name, constraints] = entry;
Expand All @@ -1775,13 +1787,9 @@ function initSearch(rawSearchIndex) {
} else {
simplifiedGenerics = binds;
}
if (mgensInout) {
for (const [fid, qid] of mgensResults.entries()) {
mgensInout.set(fid, qid);
}
}
return { simplifiedGenerics, mgens: mgensSolutionSet };
}
return simplifiedGenerics;
return { simplifiedGenerics, mgens: [mgensIn] };
}
/**
* @param {FunctionType} fnType
Expand All @@ -1805,7 +1813,7 @@ function initSearch(rawSearchIndex) {
// `fn read_all<R: Read>(R) -> Result<usize>`
// generic `R` is considered "unboxed"
return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause);
} else if (fnType.generics.length > 0 || fnType.bindings.length > 0) {
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
const simplifiedGenerics = [
...fnType.generics,
...Array.from(fnType.bindings.values()).flat(),
Expand Down
97 changes: 97 additions & 0 deletions tests/rustdoc-js/assoc-type-backtrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,101 @@ const EXPECTED = [
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
],
},
// The first two define the base case.
{
'query': 'myintofuture<fut=myfuture<t>> -> myfuture<t>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
{
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
// Unboxings of the one-argument case.
{
'query': 'myfuture<t> -> myfuture<t>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
{
'query': 'myintofuture<myfuture<t>> -> myfuture<t>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
// Invalid unboxing of the one-argument case.
// If you unbox one of the myfutures, you need to unbox both of them.
{
'query': 'myintofuture<fut=t> -> myfuture<t>',
'correction': null,
'others': [],
},
// Unboxings of the two-argument case.
{
'query': 'myintofuture<fut=t>, myintofuture<fut=t> -> t',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
{
'query': 'myintofuture<fut=myfuture>, myintofuture<fut=myfuture> -> myfuture',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
{
'query': 'myintofuture<myfuture>, myintofuture<myfuture> -> myfuture',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
{
'query': 'myfuture<t>, myfuture<t> -> myfuture<t>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
// Invalid unboxings of the two-argument case.
// If you unbox one of the myfutures, you need to unbox all of them.
{
'query': 'myintofuture<fut=t>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
'correction': null,
'others': [],
},
{
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=t> -> myfuture<t>',
'correction': null,
'others': [],
},
{
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> t',
'correction': null,
'others': [],
},
// different generics don't match up either
{
'query': 'myintofuture<fut=myfuture<u>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
'correction': null,
'others': [],
},
{
'query': 'myintofuture<output=t> -> myfuture<tt>',
'correction': null,
'others': [],
},
];
11 changes: 11 additions & 0 deletions tests/rustdoc-js/assoc-type-backtrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,14 @@ impl<'a, T, I> MyTrait for Cloned<I> where
loop {}
}
}

pub trait MyFuture {
type Output;
}

pub trait MyIntoFuture {
type Output;
type Fut: MyFuture<Output=Self::Output>;
fn into_future(self) -> Self::Fut;
fn into_future_2(self, other: Self) -> Self::Fut;
}

0 comments on commit 3673c10

Please sign in to comment.