@@ -1331,18 +1331,18 @@ function initSearch(rawSearchIndex) {
13311331 /**
13321332 * @type Map<integer, integer>|null
13331333 */
1334- let mgens = mgensIn === null ? null : new Map ( mgensIn ) ;
1334+ const mgens = mgensIn === null ? null : new Map ( mgensIn ) ;
13351335 if ( queryElems . length === 0 ) {
13361336 return ! solutionCb || solutionCb ( mgens ) ;
13371337 }
13381338 if ( ! fnTypesIn || fnTypesIn . length === 0 ) {
13391339 return false ;
13401340 }
13411341 const ql = queryElems . length ;
1342- let fl = fnTypesIn . length ;
1342+ const fl = fnTypesIn . length ;
13431343
1344- // Fast path
1345- if ( queryElems . length === 1 && queryElems [ 0 ] . generics . length === 0 ) {
1344+ // One element fast path / base case
1345+ if ( ql === 1 && queryElems [ 0 ] . generics . length === 0 ) {
13461346 const queryElem = queryElems [ 0 ] ;
13471347 for ( const fnType of fnTypesIn ) {
13481348 if ( ! unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) ) {
@@ -1396,183 +1396,113 @@ function initSearch(rawSearchIndex) {
13961396 return false ;
13971397 }
13981398
1399- // Slow path
1399+ // Multiple element recursive case
14001400 /**
14011401 * @type Array<FunctionType>
14021402 */
1403- let fnTypes = fnTypesIn . slice ( ) ;
1403+ const fnTypes = fnTypesIn . slice ( ) ;
14041404 /**
1405- * loop works by building up a solution set in the working arrays
1405+ * Algorithm works by building up a solution set in the working arrays
14061406 * fnTypes gets mutated in place to make this work, while queryElems
1407- * is left alone
1407+ * is left alone.
14081408 *
1409- * vvvvvvv `i` points here
1410- * queryElems = [ good, good, good, unknown, unknown ],
1411- * fnTypes = [ good, good, good, unknown, unknown ],
1412- * ---------------- ^^^^^^^^^^^^^^^^ `j` iterates after `i`,
1413- * | looking for candidates
1414- * everything before `i` is the
1415- * current working solution
1409+ * It works backwards, because arrays can be cheaply truncated that way.
1410+ *
1411+ * vvvvvvv `queryElem`
1412+ * queryElems = [ unknown, unknown, good, good, good ]
1413+ * fnTypes = [ unknown, unknown, good, good, good ]
1414+ * ^^^^^^^^^^^^^^^^ loop over these elements to find candidates
14161415 *
14171416 * Everything in the current working solution is known to be a good
14181417 * match, but it might not be the match we wind up going with, because
14191418 * there might be more than one candidate match, and we need to try them all
14201419 * before giving up. So, to handle this, it backtracks on failure.
1421- *
1422- * @type Array<{
1423- * "fnTypesScratch": Array<FunctionType>,
1424- * "queryElemsOffset": integer,
1425- * "fnTypesOffset": integer
1426- * }>
14271420 */
1428- const backtracking = [ ] ;
1429- let i = 0 ;
1430- let j = 0 ;
1431- const backtrack = ( ) => {
1432- while ( backtracking . length !== 0 ) {
1433- // this session failed, but there are other possible solutions
1434- // to backtrack, reset to (a copy of) the old array, do the swap or unboxing
1435- const {
1436- fnTypesScratch,
1437- mgensScratch,
1438- queryElemsOffset,
1439- fnTypesOffset,
1440- unbox,
1441- } = backtracking . pop ( ) ;
1442- mgens = mgensScratch !== null ? new Map ( mgensScratch ) : null ;
1443- const fnType = fnTypesScratch [ fnTypesOffset ] ;
1444- const queryElem = queryElems [ queryElemsOffset ] ;
1445- if ( unbox ) {
1446- if ( fnType . id < 0 ) {
1447- if ( mgens === null ) {
1448- mgens = new Map ( ) ;
1449- } else if ( mgens . has ( fnType . id ) && mgens . get ( fnType . id ) !== 0 ) {
1450- continue ;
1451- }
1452- mgens . set ( fnType . id , 0 ) ;
1453- }
1454- const generics = fnType . id < 0 ?
1455- whereClause [ ( - fnType . id ) - 1 ] :
1456- fnType . generics ;
1457- fnTypes = fnTypesScratch . toSpliced ( fnTypesOffset , 1 , ...generics ) ;
1458- fl = fnTypes . length ;
1459- // re-run the matching algorithm on this item
1460- i = queryElemsOffset - 1 ;
1461- } else {
1462- if ( fnType . id < 0 ) {
1463- if ( mgens === null ) {
1464- mgens = new Map ( ) ;
1465- } else if ( mgens . has ( fnType . id ) &&
1466- mgens . get ( fnType . id ) !== queryElem . id ) {
1467- continue ;
1468- }
1469- mgens . set ( fnType . id , queryElem . id ) ;
1470- }
1471- fnTypes = fnTypesScratch . slice ( ) ;
1472- fl = fnTypes . length ;
1473- const tmp = fnTypes [ queryElemsOffset ] ;
1474- fnTypes [ queryElemsOffset ] = fnTypes [ fnTypesOffset ] ;
1475- fnTypes [ fnTypesOffset ] = tmp ;
1476- // this is known as a good match; go to the next one
1477- i = queryElemsOffset ;
1478- }
1479- return true ;
1421+ const flast = fl - 1 ;
1422+ const qlast = ql - 1 ;
1423+ const queryElem = queryElems [ qlast ] ;
1424+ let queryElemsTmp = null ;
1425+ for ( let i = flast ; i >= 0 ; i -= 1 ) {
1426+ const fnType = fnTypes [ i ] ;
1427+ if ( ! unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1428+ continue ;
14801429 }
1481- return false ;
1482- } ;
1483- for ( i = 0 ; i !== ql ; ++ i ) {
1484- const queryElem = queryElems [ i ] ;
1485- /**
1486- * list of potential function types that go with the current query element.
1487- * @type Array<integer>
1488- */
1489- const matchCandidates = [ ] ;
1490- let fnTypesScratch = null ;
1491- let mgensScratch = null ;
1492- // don't try anything before `i`, because they've already been
1493- // paired off with the other query elements
1494- for ( j = i ; j !== fl ; ++ j ) {
1495- const fnType = fnTypes [ j ] ;
1496- if ( unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1497- if ( ! fnTypesScratch ) {
1498- fnTypesScratch = fnTypes . slice ( ) ;
1430+ let mgensScratch ;
1431+ if ( fnType . id < 0 ) {
1432+ mgensScratch = new Map ( mgens ) ;
1433+ if ( mgensScratch . has ( fnType . id )
1434+ && mgensScratch . get ( fnType . id ) !== queryElem . id ) {
1435+ continue ;
1436+ }
1437+ mgensScratch . set ( fnType . id , queryElem . id ) ;
1438+ } else {
1439+ mgensScratch = mgens ;
1440+ }
1441+ // fnTypes[i] is a potential match
1442+ // fnTypes[flast] is the last item in the list
1443+ // swap them, and drop the potential match from the list
1444+ // check if the remaining function types also match
1445+ fnTypes [ i ] = fnTypes [ flast ] ;
1446+ fnTypes . length = flast ;
1447+ if ( ! queryElemsTmp ) {
1448+ queryElemsTmp = queryElems . slice ( 0 , qlast ) ;
1449+ }
1450+ const passesUnification = unifyFunctionTypes (
1451+ fnTypes ,
1452+ queryElemsTmp ,
1453+ whereClause ,
1454+ mgensScratch ,
1455+ mgensScratch => {
1456+ if ( fnType . generics . length === 0 && queryElem . generics . length === 0 ) {
1457+ return ! solutionCb || solutionCb ( mgensScratch ) ;
14991458 }
1500- unifyFunctionTypes (
1459+ return unifyFunctionTypes (
15011460 fnType . generics ,
15021461 queryElem . generics ,
15031462 whereClause ,
1504- mgens ,
1505- mgensScratch => {
1506- matchCandidates . push ( {
1507- fnTypesScratch,
1508- mgensScratch,
1509- queryElemsOffset : i ,
1510- fnTypesOffset : j ,
1511- unbox : false ,
1512- } ) ;
1513- return false ; // "reject" all candidates to gather all of them
1514- }
1515- ) ;
1516- }
1517- if ( unifyFunctionTypeIsUnboxCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1518- if ( ! fnTypesScratch ) {
1519- fnTypesScratch = fnTypes . slice ( ) ;
1520- }
1521- if ( ! mgensScratch && mgens !== null ) {
1522- mgensScratch = new Map ( mgens ) ;
1523- }
1524- backtracking . push ( {
1525- fnTypesScratch,
15261463 mgensScratch ,
1527- queryElemsOffset : i ,
1528- fnTypesOffset : j ,
1529- unbox : true ,
1530- } ) ;
1531- }
1532- }
1533- if ( matchCandidates . length === 0 ) {
1534- if ( backtrack ( ) ) {
1535- continue ;
1536- } else {
1537- return false ;
1538- }
1539- }
1540- // use the current candidate
1541- const { fnTypesOffset : candidate , mgensScratch : mgensNew } = matchCandidates . pop ( ) ;
1542- if ( fnTypes [ candidate ] . id < 0 && queryElems [ i ] . id < 0 ) {
1543- if ( mgens === null ) {
1544- mgens = new Map ( ) ;
1464+ solutionCb
1465+ ) ;
15451466 }
1546- mgens . set ( fnTypes [ candidate ] . id , queryElems [ i ] . id ) ;
1467+ ) ;
1468+ if ( passesUnification ) {
1469+ return true ;
15471470 }
1548- if ( mgensNew !== null ) {
1549- if ( mgens === null ) {
1550- mgens = mgensNew ;
1551- } else {
1552- for ( const [ fid , qid ] of mgensNew ) {
1553- mgens . set ( fid , qid ) ;
1554- }
1555- }
1471+ // backtrack
1472+ fnTypes [ flast ] = fnTypes [ i ] ;
1473+ fnTypes [ i ] = fnType ;
1474+ fnTypes . length = fl ;
1475+ }
1476+ for ( let i = flast ; i >= 0 ; i -= 1 ) {
1477+ const fnType = fnTypes [ i ] ;
1478+ if ( ! unifyFunctionTypeIsUnboxCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1479+ continue ;
15561480 }
1557- // `i` and `j` are paired off
1558- // `queryElems[i]` is left in place
1559- // `fnTypes[j]` is swapped with `fnTypes[i]` to pair them off
1560- const tmp = fnTypes [ candidate ] ;
1561- fnTypes [ candidate ] = fnTypes [ i ] ;
1562- fnTypes [ i ] = tmp ;
1563- // write other candidates to backtracking queue
1564- for ( const otherCandidate of matchCandidates ) {
1565- backtracking . push ( otherCandidate ) ;
1566- }
1567- // If we're on the last item, check the solution with the callback
1568- // backtrack if the callback says its unsuitable
1569- while ( i === ( ql - 1 ) && solutionCb && ! solutionCb ( mgens ) ) {
1570- if ( ! backtrack ( ) ) {
1571- return false ;
1481+ let mgensScratch ;
1482+ if ( fnType . id < 0 ) {
1483+ mgensScratch = new Map ( mgens ) ;
1484+ if ( mgensScratch . has ( fnType . id ) && mgensScratch . get ( fnType . id ) !== 0 ) {
1485+ continue ;
15721486 }
1487+ mgensScratch . set ( fnType . id , 0 ) ;
1488+ } else {
1489+ mgensScratch = mgens ;
1490+ }
1491+ const generics = fnType . id < 0 ?
1492+ whereClause [ ( - fnType . id ) - 1 ] :
1493+ fnType . generics ;
1494+ const passesUnification = unifyFunctionTypes (
1495+ fnTypes . toSpliced ( i , 1 , ...generics ) ,
1496+ queryElems ,
1497+ whereClause ,
1498+ mgensScratch ,
1499+ solutionCb
1500+ ) ;
1501+ if ( passesUnification ) {
1502+ return true ;
15731503 }
15741504 }
1575- return true ;
1505+ return false ;
15761506 }
15771507 function unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) {
15781508 // type filters look like `trait:Read` or `enum:Result`
0 commit comments