@@ -1331,18 +1331,18 @@ function initSearch(rawSearchIndex) {
1331
1331
/**
1332
1332
* @type Map<integer, integer>|null
1333
1333
*/
1334
- let mgens = mgensIn === null ? null : new Map ( mgensIn ) ;
1334
+ const mgens = mgensIn === null ? null : new Map ( mgensIn ) ;
1335
1335
if ( queryElems . length === 0 ) {
1336
1336
return ! solutionCb || solutionCb ( mgens ) ;
1337
1337
}
1338
1338
if ( ! fnTypesIn || fnTypesIn . length === 0 ) {
1339
1339
return false ;
1340
1340
}
1341
1341
const ql = queryElems . length ;
1342
- let fl = fnTypesIn . length ;
1342
+ const fl = fnTypesIn . length ;
1343
1343
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 ) {
1346
1346
const queryElem = queryElems [ 0 ] ;
1347
1347
for ( const fnType of fnTypesIn ) {
1348
1348
if ( ! unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) ) {
@@ -1396,183 +1396,113 @@ function initSearch(rawSearchIndex) {
1396
1396
return false ;
1397
1397
}
1398
1398
1399
- // Slow path
1399
+ // Multiple element recursive case
1400
1400
/**
1401
1401
* @type Array<FunctionType>
1402
1402
*/
1403
- let fnTypes = fnTypesIn . slice ( ) ;
1403
+ const fnTypes = fnTypesIn . slice ( ) ;
1404
1404
/**
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
1406
1406
* fnTypes gets mutated in place to make this work, while queryElems
1407
- * is left alone
1407
+ * is left alone.
1408
1408
*
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
1416
1415
*
1417
1416
* Everything in the current working solution is known to be a good
1418
1417
* match, but it might not be the match we wind up going with, because
1419
1418
* there might be more than one candidate match, and we need to try them all
1420
1419
* 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
- * }>
1427
1420
*/
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 ;
1480
1429
}
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 ) ;
1499
1458
}
1500
- unifyFunctionTypes (
1459
+ return unifyFunctionTypes (
1501
1460
fnType . generics ,
1502
1461
queryElem . generics ,
1503
1462
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,
1526
1463
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
+ ) ;
1545
1466
}
1546
- mgens . set ( fnTypes [ candidate ] . id , queryElems [ i ] . id ) ;
1467
+ ) ;
1468
+ if ( passesUnification ) {
1469
+ return true ;
1547
1470
}
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 ;
1556
1480
}
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 ;
1572
1486
}
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 ;
1573
1503
}
1574
1504
}
1575
- return true ;
1505
+ return false ;
1576
1506
}
1577
1507
function unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) {
1578
1508
// type filters look like `trait:Read` or `enum:Result`
0 commit comments