@@ -1318,7 +1318,7 @@ function initSearch(rawSearchIndex) {
1318
1318
* then this function will try with a different solution, or bail with false if it
1319
1319
* runs out of candidates.
1320
1320
*
1321
- * @param {Array<FunctionType> } fnTypes - The objects to check.
1321
+ * @param {Array<FunctionType> } fnTypesIn - The objects to check.
1322
1322
* @param {Array<QueryElement> } queryElems - The elements from the parsed query.
1323
1323
* @param {[FunctionType] } whereClause - Trait bounds for generic items.
1324
1324
* @param {Map<number,number>|null } mgensIn
@@ -1329,179 +1329,180 @@ function initSearch(rawSearchIndex) {
1329
1329
*/
1330
1330
function unifyFunctionTypes ( fnTypesIn , queryElems , whereClause , mgensIn , solutionCb ) {
1331
1331
/**
1332
- * @type Map<integer, integer>
1332
+ * @type Map<integer, integer>|null
1333
1333
*/
1334
- let mgens = 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
+
1344
+ // One element fast path / base case
1345
+ if ( ql === 1 && queryElems [ 0 ] . generics . length === 0 ) {
1346
+ const queryElem = queryElems [ 0 ] ;
1347
+ for ( const fnType of fnTypesIn ) {
1348
+ if ( ! unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1349
+ continue ;
1350
+ }
1351
+ if ( fnType . id < 0 && queryElem . id < 0 ) {
1352
+ if ( mgens && mgens . has ( fnType . id ) &&
1353
+ mgens . get ( fnType . id ) !== queryElem . id ) {
1354
+ continue ;
1355
+ }
1356
+ const mgensScratch = new Map ( mgens ) ;
1357
+ mgensScratch . set ( fnType . id , queryElem . id ) ;
1358
+ if ( ! solutionCb || solutionCb ( mgensScratch ) ) {
1359
+ return true ;
1360
+ }
1361
+ } else if ( ! solutionCb || solutionCb ( mgens ? new Map ( mgens ) : null ) ) {
1362
+ // unifyFunctionTypeIsMatchCandidate already checks that ids match
1363
+ return true ;
1364
+ }
1365
+ }
1366
+ for ( const fnType of fnTypesIn ) {
1367
+ if ( ! unifyFunctionTypeIsUnboxCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1368
+ continue ;
1369
+ }
1370
+ if ( fnType . id < 0 ) {
1371
+ if ( mgens && mgens . has ( fnType . id ) &&
1372
+ mgens . get ( fnType . id ) !== 0 ) {
1373
+ continue ;
1374
+ }
1375
+ const mgensScratch = new Map ( mgens ) ;
1376
+ mgensScratch . set ( fnType . id , 0 ) ;
1377
+ if ( unifyFunctionTypes (
1378
+ whereClause [ ( - fnType . id ) - 1 ] ,
1379
+ queryElems ,
1380
+ whereClause ,
1381
+ mgensScratch ,
1382
+ solutionCb
1383
+ ) ) {
1384
+ return true ;
1385
+ }
1386
+ } else if ( unifyFunctionTypes (
1387
+ fnType . generics ,
1388
+ queryElems ,
1389
+ whereClause ,
1390
+ mgens ? new Map ( mgens ) : null ,
1391
+ solutionCb
1392
+ ) ) {
1393
+ return true ;
1394
+ }
1395
+ }
1396
+ return false ;
1397
+ }
1398
+
1399
+ // Multiple element recursive case
1343
1400
/**
1344
1401
* @type Array<FunctionType>
1345
1402
*/
1346
- let fnTypes = fnTypesIn . slice ( ) ;
1403
+ const fnTypes = fnTypesIn . slice ( ) ;
1347
1404
/**
1348
- * 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
1349
1406
* fnTypes gets mutated in place to make this work, while queryElems
1350
- * is left alone
1407
+ * is left alone.
1351
1408
*
1352
- * vvvvvvv `i` points here
1353
- * queryElems = [ good, good, good, unknown, unknown ],
1354
- * fnTypes = [ good, good, good, unknown, unknown ],
1355
- * ---------------- ^^^^^^^^^^^^^^^^ `j` iterates after `i`,
1356
- * | looking for candidates
1357
- * everything before `i` is the
1358
- * 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
1359
1415
*
1360
1416
* Everything in the current working solution is known to be a good
1361
1417
* match, but it might not be the match we wind up going with, because
1362
1418
* there might be more than one candidate match, and we need to try them all
1363
1419
* before giving up. So, to handle this, it backtracks on failure.
1364
- *
1365
- * @type Array<{
1366
- * "fnTypesScratch": Array<FunctionType>,
1367
- * "queryElemsOffset": integer,
1368
- * "fnTypesOffset": integer
1369
- * }>
1370
1420
*/
1371
- const backtracking = [ ] ;
1372
- let i = 0 ;
1373
- let j = 0 ;
1374
- const backtrack = ( ) => {
1375
- while ( backtracking . length !== 0 ) {
1376
- // this session failed, but there are other possible solutions
1377
- // to backtrack, reset to (a copy of) the old array, do the swap or unboxing
1378
- const {
1379
- fnTypesScratch,
1380
- mgensScratch,
1381
- queryElemsOffset,
1382
- fnTypesOffset,
1383
- unbox,
1384
- } = backtracking . pop ( ) ;
1385
- mgens = new Map ( mgensScratch ) ;
1386
- const fnType = fnTypesScratch [ fnTypesOffset ] ;
1387
- const queryElem = queryElems [ queryElemsOffset ] ;
1388
- if ( unbox ) {
1389
- if ( fnType . id < 0 ) {
1390
- if ( mgens . has ( fnType . id ) && mgens . get ( fnType . id ) !== 0 ) {
1391
- continue ;
1392
- }
1393
- mgens . set ( fnType . id , 0 ) ;
1394
- }
1395
- const generics = fnType . id < 0 ?
1396
- whereClause [ ( - fnType . id ) - 1 ] :
1397
- fnType . generics ;
1398
- fnTypes = fnTypesScratch . toSpliced ( fnTypesOffset , 1 , ...generics ) ;
1399
- fl = fnTypes . length ;
1400
- // re-run the matching algorithm on this item
1401
- i = queryElemsOffset - 1 ;
1402
- } else {
1403
- if ( fnType . id < 0 ) {
1404
- if ( mgens . has ( fnType . id ) && mgens . get ( fnType . id ) !== queryElem . id ) {
1405
- continue ;
1406
- }
1407
- mgens . set ( fnType . id , queryElem . id ) ;
1408
- }
1409
- fnTypes = fnTypesScratch . slice ( ) ;
1410
- fl = fnTypes . length ;
1411
- const tmp = fnTypes [ queryElemsOffset ] ;
1412
- fnTypes [ queryElemsOffset ] = fnTypes [ fnTypesOffset ] ;
1413
- fnTypes [ fnTypesOffset ] = tmp ;
1414
- // this is known as a good match; go to the next one
1415
- i = queryElemsOffset ;
1416
- }
1417
- 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 ;
1418
1429
}
1419
- return false ;
1420
- } ;
1421
- for ( i = 0 ; i !== ql ; ++ i ) {
1422
- const queryElem = queryElems [ i ] ;
1423
- /**
1424
- * list of potential function types that go with the current query element.
1425
- * @type Array<integer>
1426
- */
1427
- const matchCandidates = [ ] ;
1428
- let fnTypesScratch = null ;
1429
- let mgensScratch = null ;
1430
- // don't try anything before `i`, because they've already been
1431
- // paired off with the other query elements
1432
- for ( j = i ; j !== fl ; ++ j ) {
1433
- const fnType = fnTypes [ j ] ;
1434
- if ( unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1435
- if ( ! fnTypesScratch ) {
1436
- 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 ) ;
1437
1458
}
1438
- unifyFunctionTypes (
1459
+ return unifyFunctionTypes (
1439
1460
fnType . generics ,
1440
1461
queryElem . generics ,
1441
1462
whereClause ,
1442
- mgens ,
1443
- mgensScratch => {
1444
- matchCandidates . push ( {
1445
- fnTypesScratch,
1446
- mgensScratch,
1447
- queryElemsOffset : i ,
1448
- fnTypesOffset : j ,
1449
- unbox : false ,
1450
- } ) ;
1451
- return false ; // "reject" all candidates to gather all of them
1452
- }
1453
- ) ;
1454
- }
1455
- if ( unifyFunctionTypeIsUnboxCandidate ( fnType , queryElem , whereClause , mgens ) ) {
1456
- if ( ! fnTypesScratch ) {
1457
- fnTypesScratch = fnTypes . slice ( ) ;
1458
- }
1459
- if ( ! mgensScratch ) {
1460
- mgensScratch = new Map ( mgens ) ;
1461
- }
1462
- backtracking . push ( {
1463
- fnTypesScratch,
1464
1463
mgensScratch ,
1465
- queryElemsOffset : i ,
1466
- fnTypesOffset : j ,
1467
- unbox : true ,
1468
- } ) ;
1464
+ solutionCb
1465
+ ) ;
1469
1466
}
1467
+ ) ;
1468
+ if ( passesUnification ) {
1469
+ return true ;
1470
1470
}
1471
- if ( matchCandidates . length === 0 ) {
1472
- if ( backtrack ( ) ) {
1473
- continue ;
1474
- } else {
1475
- return false ;
1476
- }
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 ;
1477
1480
}
1478
- // use the current candidate
1479
- const { fnTypesOffset : candidate , mgensScratch : mgensNew } = matchCandidates . pop ( ) ;
1480
- if ( fnTypes [ candidate ] . id < 0 && queryElems [ i ] . id < 0 ) {
1481
- mgens . set ( fnTypes [ candidate ] . id , queryElems [ i ] . id ) ;
1482
- }
1483
- for ( const [ fid , qid ] of mgensNew ) {
1484
- mgens . set ( fid , qid ) ;
1485
- }
1486
- // `i` and `j` are paired off
1487
- // `queryElems[i]` is left in place
1488
- // `fnTypes[j]` is swapped with `fnTypes[i]` to pair them off
1489
- const tmp = fnTypes [ candidate ] ;
1490
- fnTypes [ candidate ] = fnTypes [ i ] ;
1491
- fnTypes [ i ] = tmp ;
1492
- // write other candidates to backtracking queue
1493
- for ( const otherCandidate of matchCandidates ) {
1494
- backtracking . push ( otherCandidate ) ;
1495
- }
1496
- // If we're on the last item, check the solution with the callback
1497
- // backtrack if the callback says its unsuitable
1498
- while ( i === ( ql - 1 ) && solutionCb && ! solutionCb ( mgens ) ) {
1499
- if ( ! backtrack ( ) ) {
1500
- 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 ;
1501
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 ;
1502
1503
}
1503
1504
}
1504
- return true ;
1505
+ return false ;
1505
1506
}
1506
1507
function unifyFunctionTypeIsMatchCandidate ( fnType , queryElem , whereClause , mgens ) {
1507
1508
// type filters look like `trait:Read` or `enum:Result`
@@ -1514,15 +1515,17 @@ function initSearch(rawSearchIndex) {
1514
1515
// or, if mgens[fnType.id] = 0, then we've matched this generic with a bare trait
1515
1516
// and should make that same decision everywhere it appears
1516
1517
if ( fnType . id < 0 && queryElem . id < 0 ) {
1517
- if ( mgens . has ( fnType . id ) && mgens . get ( fnType . id ) !== queryElem . id ) {
1518
- return false ;
1519
- }
1520
- for ( const [ fid , qid ] of mgens . entries ( ) ) {
1521
- if ( fnType . id !== fid && queryElem . id === qid ) {
1518
+ if ( mgens !== null ) {
1519
+ if ( mgens . has ( fnType . id ) && mgens . get ( fnType . id ) !== queryElem . id ) {
1522
1520
return false ;
1523
1521
}
1524
- if ( fnType . id === fid && queryElem . id !== qid ) {
1525
- return false ;
1522
+ for ( const [ fid , qid ] of mgens . entries ( ) ) {
1523
+ if ( fnType . id !== fid && queryElem . id === qid ) {
1524
+ return false ;
1525
+ }
1526
+ if ( fnType . id === fid && queryElem . id !== qid ) {
1527
+ return false ;
1528
+ }
1526
1529
}
1527
1530
}
1528
1531
} else {
@@ -1575,7 +1578,7 @@ function initSearch(rawSearchIndex) {
1575
1578
}
1576
1579
// mgens[fnType.id] === 0 indicates that we committed to unboxing this generic
1577
1580
// mgens[fnType.id] === null indicates that we haven't decided yet
1578
- if ( mgens . has ( fnType . id ) && mgens . get ( fnType . id ) !== 0 ) {
1581
+ if ( mgens !== null && mgens . has ( fnType . id ) && mgens . get ( fnType . id ) !== 0 ) {
1579
1582
return false ;
1580
1583
}
1581
1584
// This is only a potential unbox if the search query appears in the where clause
0 commit comments