Skip to content

Commit 81bfeba

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 dcce41e commit 81bfeba

File tree

3 files changed

+162
-52
lines changed

3 files changed

+162
-52
lines changed

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

Lines changed: 54 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,33 +1506,36 @@ function initSearch(rawSearchIndex) {
15061506
for (j = i; j !== fl; ++j) {
15071507
const fnType = fnTypes[j];
15081508
if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
1509-
const mgensScratch = new Map(mgens);
1510-
const simplifiedGenerics = unifyFunctionTypeCheckBindings(
1509+
const solution = unifyFunctionTypeCheckBindings(
15111510
fnType,
15121511
queryElem,
15131512
whereClause,
1514-
mgensScratch
1513+
mgens
15151514
);
1516-
if (simplifiedGenerics) {
1515+
if (solution) {
15171516
if (!fnTypesScratch) {
15181517
fnTypesScratch = fnTypes.slice();
15191518
}
1520-
unifyFunctionTypes(
1521-
simplifiedGenerics,
1522-
queryElem.generics,
1523-
whereClause,
1524-
mgensScratch,
1525-
mgensScratch => {
1526-
matchCandidates.push({
1527-
fnTypesScratch,
1528-
mgensScratch,
1529-
queryElemsOffset: i,
1530-
fnTypesOffset: j,
1531-
unbox: false,
1532-
});
1533-
return false; // "reject" all candidates to gather all of them
1534-
}
1535-
);
1519+
const simplifiedGenerics = solution.simplifiedGenerics;
1520+
for (const solutionMgens of solution.mgens) {
1521+
unifyFunctionTypes(
1522+
simplifiedGenerics,
1523+
queryElem.generics,
1524+
whereClause,
1525+
solutionMgens,
1526+
mgensScratch => {
1527+
matchCandidates.push({
1528+
fnTypesScratch,
1529+
mgensScratch,
1530+
queryElemsOffset: i,
1531+
fnTypesOffset: j,
1532+
unbox: false,
1533+
});
1534+
// "reject" all candidates to gather all of them
1535+
return false;
1536+
}
1537+
);
1538+
}
15361539
}
15371540
}
15381541
if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
@@ -1687,41 +1690,44 @@ function initSearch(rawSearchIndex) {
16871690
* @param {FunctionType} fnType
16881691
* @param {QueryElement} queryElem
16891692
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
1690-
* @param {Map<number,number>|null} mgensInout - Map functions generics to query generics.
1691-
* Written on success.
1692-
* @returns {boolean|FunctionType[]}
1693+
* @param {Map<number,number>} mgensIn - Map functions generics to query generics.
1694+
* Never modified.
1695+
* @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
16931696
*/
1694-
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensInout) {
1695-
// Simplify generics now
1696-
let simplifiedGenerics = fnType.generics;
1697-
if (!simplifiedGenerics) {
1698-
simplifiedGenerics = [];
1699-
}
1697+
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
17001698
if (fnType.bindings.size < queryElem.bindings.size) {
17011699
return false;
17021700
}
1701+
let simplifiedGenerics = fnType.generics || [];
17031702
if (fnType.bindings.size > 0) {
1704-
const mgensResults = new Map(mgensInout);
1703+
let mgensSolutionSet = [mgensIn];
17051704
for (const [name, constraints] of queryElem.bindings.entries()) {
1706-
if (!fnType.bindings.has(name)) {
1705+
if (mgensSolutionSet.length === 0) {
17071706
return false;
17081707
}
1709-
// Since both items must have exactly one entry per name,
1710-
// we don't need to backtrack here, but do need to write mgens.
1711-
if (!unifyFunctionTypes(
1712-
fnType.bindings.get(name),
1713-
constraints,
1714-
whereClause,
1715-
mgensResults,
1716-
mgens => {
1717-
for (const [fid, qid] of mgens.entries()) {
1718-
mgensResults.set(fid, qid);
1719-
}
1720-
return true;
1721-
}
1722-
)) {
1708+
if (!fnType.bindings.has(name)) {
17231709
return false;
17241710
}
1711+
const fnTypeBindings = fnType.bindings.get(name);
1712+
mgensSolutionSet = mgensSolutionSet.flatMap(mgens => {
1713+
const newSolutions = [];
1714+
unifyFunctionTypes(
1715+
fnTypeBindings,
1716+
constraints,
1717+
whereClause,
1718+
mgens,
1719+
newMgens => {
1720+
newSolutions.push(newMgens);
1721+
// return `false` makes unifyFunctionTypes return the full set of
1722+
// possible solutions
1723+
return false;
1724+
}
1725+
);
1726+
return newSolutions;
1727+
});
1728+
}
1729+
if (mgensSolutionSet.length === 0) {
1730+
return false;
17251731
}
17261732
const binds = Array.from(fnType.bindings.entries()).flatMap(entry => {
17271733
const [name, constraints] = entry;
@@ -1736,13 +1742,9 @@ function initSearch(rawSearchIndex) {
17361742
} else {
17371743
simplifiedGenerics = binds;
17381744
}
1739-
if (mgensInout) {
1740-
for (const [fid, qid] of mgensResults.entries()) {
1741-
mgensInout.set(fid, qid);
1742-
}
1743-
}
1745+
return { simplifiedGenerics, mgens: mgensSolutionSet };
17441746
}
1745-
return simplifiedGenerics;
1747+
return { simplifiedGenerics, mgens: [mgensIn] };
17461748
}
17471749
/**
17481750
* @param {FunctionType} fnType
@@ -1766,7 +1768,7 @@ function initSearch(rawSearchIndex) {
17661768
// `fn read_all<R: Read>(R) -> Result<usize>`
17671769
// generic `R` is considered "unboxed"
17681770
return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause);
1769-
} else if (fnType.generics.length > 0 || fnType.bindings.length > 0) {
1771+
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
17701772
const simplifiedGenerics = [
17711773
...fnType.generics,
17721774
...Array.from(fnType.bindings.values()).flat(),

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

Lines changed: 97 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
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)