Skip to content

Commit fd7e5f8

Browse files
committed
rustdoc: fix corner cases in unboxing and type parameters
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.
1 parent b4dec52 commit fd7e5f8

File tree

3 files changed

+180
-64
lines changed

3 files changed

+180
-64
lines changed

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

+72-64
Original file line numberDiff line numberDiff line change
@@ -1389,29 +1389,35 @@ function initSearch(rawSearchIndex) {
13891389
* @return {boolean} - Returns true if a match, false otherwise.
13901390
*/
13911391
function checkGenerics(fnType, queryElem, whereClause, mgensInout) {
1392-
const simplifiedGenerics = unifyFunctionTypeCheckBindings(
1392+
const solutions = unifyFunctionTypeCheckBindings(
13931393
fnType,
13941394
queryElem,
13951395
whereClause,
13961396
mgensInout
13971397
);
1398-
if (!simplifiedGenerics) {
1398+
if (!solutions) {
13991399
return false;
14001400
}
1401-
return unifyFunctionTypes(
1402-
simplifiedGenerics,
1403-
queryElem.generics,
1404-
whereClause,
1405-
mgensInout,
1406-
mgens => {
1407-
if (mgensInout) {
1408-
for (const [fid, qid] of mgens.entries()) {
1409-
mgensInout.set(fid, qid);
1401+
const simplifiedGenerics = solutions.simplifiedGenerics;
1402+
for (const mgens of solutions.mgens) {
1403+
if (unifyFunctionTypes(
1404+
simplifiedGenerics,
1405+
queryElem.generics,
1406+
whereClause,
1407+
mgens,
1408+
mgens => {
1409+
if (mgensInout) {
1410+
for (const [fid, qid] of mgens.entries()) {
1411+
mgensInout.set(fid, qid);
1412+
}
14101413
}
1414+
return true;
14111415
}
1416+
)) {
14121417
return true;
14131418
}
1414-
);
1419+
}
1420+
return false;
14151421
}
14161422
/**
14171423
* This function checks if a list of search query `queryElems` can all be found in the
@@ -1545,33 +1551,36 @@ function initSearch(rawSearchIndex) {
15451551
for (j = i; j !== fl; ++j) {
15461552
const fnType = fnTypes[j];
15471553
if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
1548-
const mgensScratch = new Map(mgens);
1549-
const simplifiedGenerics = unifyFunctionTypeCheckBindings(
1554+
const solution = unifyFunctionTypeCheckBindings(
15501555
fnType,
15511556
queryElem,
15521557
whereClause,
1553-
mgensScratch
1558+
mgens
15541559
);
1555-
if (simplifiedGenerics) {
1560+
if (solution) {
15561561
if (!fnTypesScratch) {
15571562
fnTypesScratch = fnTypes.slice();
15581563
}
1559-
unifyFunctionTypes(
1560-
simplifiedGenerics,
1561-
queryElem.generics,
1562-
whereClause,
1563-
mgensScratch,
1564-
mgensScratch => {
1565-
matchCandidates.push({
1566-
fnTypesScratch,
1567-
mgensScratch,
1568-
queryElemsOffset: i,
1569-
fnTypesOffset: j,
1570-
unbox: false,
1571-
});
1572-
return false; // "reject" all candidates to gather all of them
1573-
}
1574-
);
1564+
const simplifiedGenerics = solution.simplifiedGenerics;
1565+
for (const solutionMgens of solution.mgens) {
1566+
unifyFunctionTypes(
1567+
simplifiedGenerics,
1568+
queryElem.generics,
1569+
whereClause,
1570+
solutionMgens,
1571+
mgensScratch => {
1572+
matchCandidates.push({
1573+
fnTypesScratch,
1574+
mgensScratch,
1575+
queryElemsOffset: i,
1576+
fnTypesOffset: j,
1577+
unbox: false,
1578+
});
1579+
// "reject" all candidates to gather all of them
1580+
return false;
1581+
}
1582+
);
1583+
}
15751584
}
15761585
}
15771586
if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
@@ -1726,41 +1735,44 @@ function initSearch(rawSearchIndex) {
17261735
* @param {FunctionType} fnType
17271736
* @param {QueryElement} queryElem
17281737
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
1729-
* @param {Map<number,number>|null} mgensInout - Map functions generics to query generics.
1730-
* Written on success.
1731-
* @returns {boolean|FunctionType[]}
1738+
* @param {Map<number,number>} mgensIn - Map functions generics to query generics.
1739+
* Never modified.
1740+
* @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
17321741
*/
1733-
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensInout) {
1734-
// Simplify generics now
1735-
let simplifiedGenerics = fnType.generics;
1736-
if (!simplifiedGenerics) {
1737-
simplifiedGenerics = [];
1738-
}
1742+
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
17391743
if (fnType.bindings.size < queryElem.bindings.size) {
17401744
return false;
17411745
}
1746+
let simplifiedGenerics = fnType.generics || [];
17421747
if (fnType.bindings.size > 0) {
1743-
const mgensResults = new Map(mgensInout);
1748+
let mgensSolutionSet = [mgensIn];
17441749
for (const [name, constraints] of queryElem.bindings.entries()) {
1745-
if (!fnType.bindings.has(name)) {
1750+
if (mgensSolutionSet.length === 0) {
17461751
return false;
17471752
}
1748-
// Since both items must have exactly one entry per name,
1749-
// we don't need to backtrack here, but do need to write mgens.
1750-
if (!unifyFunctionTypes(
1751-
fnType.bindings.get(name),
1752-
constraints,
1753-
whereClause,
1754-
mgensResults,
1755-
mgens => {
1756-
for (const [fid, qid] of mgens.entries()) {
1757-
mgensResults.set(fid, qid);
1758-
}
1759-
return true;
1760-
}
1761-
)) {
1753+
if (!fnType.bindings.has(name)) {
17621754
return false;
17631755
}
1756+
const fnTypeBindings = fnType.bindings.get(name);
1757+
mgensSolutionSet = mgensSolutionSet.flatMap(mgens => {
1758+
const newSolutions = [];
1759+
unifyFunctionTypes(
1760+
fnTypeBindings,
1761+
constraints,
1762+
whereClause,
1763+
mgens,
1764+
newMgens => {
1765+
newSolutions.push(newMgens);
1766+
// return `false` makes unifyFunctionTypes return the full set of
1767+
// possible solutions
1768+
return false;
1769+
}
1770+
);
1771+
return newSolutions;
1772+
});
1773+
}
1774+
if (mgensSolutionSet.length === 0) {
1775+
return false;
17641776
}
17651777
const binds = Array.from(fnType.bindings.entries()).flatMap(entry => {
17661778
const [name, constraints] = entry;
@@ -1775,13 +1787,9 @@ function initSearch(rawSearchIndex) {
17751787
} else {
17761788
simplifiedGenerics = binds;
17771789
}
1778-
if (mgensInout) {
1779-
for (const [fid, qid] of mgensResults.entries()) {
1780-
mgensInout.set(fid, qid);
1781-
}
1782-
}
1790+
return { simplifiedGenerics, mgens: mgensSolutionSet };
17831791
}
1784-
return simplifiedGenerics;
1792+
return { simplifiedGenerics, mgens: [mgensIn] };
17851793
}
17861794
/**
17871795
* @param {FunctionType} fnType
@@ -1805,7 +1813,7 @@ function initSearch(rawSearchIndex) {
18051813
// `fn read_all<R: Read>(R) -> Result<usize>`
18061814
// generic `R` is considered "unboxed"
18071815
return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause);
1808-
} else if (fnType.generics.length > 0 || fnType.bindings.length > 0) {
1816+
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
18091817
const simplifiedGenerics = [
18101818
...fnType.generics,
18111819
...Array.from(fnType.bindings.values()).flat(),

tests/rustdoc-js/assoc-type-backtrack.js

+97
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,101 @@ const EXPECTED = [
6363
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
6464
],
6565
},
66+
// The first two define the base case.
67+
{
68+
'query': 'myintofuture<fut=myfuture<t>> -> myfuture<t>',
69+
'correction': null,
70+
'others': [
71+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
72+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
73+
],
74+
},
75+
{
76+
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
77+
'correction': null,
78+
'others': [
79+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
80+
],
81+
},
82+
// Unboxings of the one-argument case.
83+
{
84+
'query': 'myfuture<t> -> myfuture<t>',
85+
'correction': null,
86+
'others': [
87+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
88+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
89+
],
90+
},
91+
{
92+
'query': 'myintofuture<myfuture<t>> -> myfuture<t>',
93+
'correction': null,
94+
'others': [
95+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
96+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
97+
],
98+
},
99+
// Invalid unboxing of the one-argument case.
100+
// If you unbox one of the myfutures, you need to unbox both of them.
101+
{
102+
'query': 'myintofuture<fut=t> -> myfuture<t>',
103+
'correction': null,
104+
'others': [],
105+
},
106+
// Unboxings of the two-argument case.
107+
{
108+
'query': 'myintofuture<fut=t>, myintofuture<fut=t> -> t',
109+
'correction': null,
110+
'others': [
111+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
112+
],
113+
},
114+
{
115+
'query': 'myintofuture<fut=myfuture>, myintofuture<fut=myfuture> -> myfuture',
116+
'correction': null,
117+
'others': [
118+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
119+
],
120+
},
121+
{
122+
'query': 'myintofuture<myfuture>, myintofuture<myfuture> -> myfuture',
123+
'correction': null,
124+
'others': [
125+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
126+
],
127+
},
128+
{
129+
'query': 'myfuture<t>, myfuture<t> -> myfuture<t>',
130+
'correction': null,
131+
'others': [
132+
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
133+
],
134+
},
135+
// Invalid unboxings of the two-argument case.
136+
// If you unbox one of the myfutures, you need to unbox all of them.
137+
{
138+
'query': 'myintofuture<fut=t>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
139+
'correction': null,
140+
'others': [],
141+
},
142+
{
143+
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=t> -> myfuture<t>',
144+
'correction': null,
145+
'others': [],
146+
},
147+
{
148+
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> t',
149+
'correction': null,
150+
'others': [],
151+
},
152+
// different generics don't match up either
153+
{
154+
'query': 'myintofuture<fut=myfuture<u>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
155+
'correction': null,
156+
'others': [],
157+
},
158+
{
159+
'query': 'myintofuture<output=t> -> myfuture<tt>',
160+
'correction': null,
161+
'others': [],
162+
},
66163
];

tests/rustdoc-js/assoc-type-backtrack.rs

+11
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,14 @@ impl<'a, T, I> MyTrait for Cloned<I> where
2525
loop {}
2626
}
2727
}
28+
29+
pub trait MyFuture {
30+
type Output;
31+
}
32+
33+
pub trait MyIntoFuture {
34+
type Output;
35+
type Fut: MyFuture<Output=Self::Output>;
36+
fn into_future(self) -> Self::Fut;
37+
fn into_future_2(self, other: Self) -> Self::Fut;
38+
}

0 commit comments

Comments
 (0)