Skip to content

Commit c39e24c

Browse files
authored
fix(patterns): Update containerHasSplit(copyArray, ...) to return copyArray results (#3065)
Fixes #3064
2 parents 8297f41 + c488503 commit c39e24c

File tree

3 files changed

+300
-80
lines changed

3 files changed

+300
-80
lines changed

.changeset/short-horses-buy.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@endo/patterns': patch
3+
---
4+
5+
- `containerHasSplit` now hardens its output(s) when working with copyArrays,
6+
ensuring that each output is itself a copyArray instance.

packages/patterns/src/patterns/patternMatchers.js

Lines changed: 69 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @ts-nocheck So many errors that the suppressions hamper readability.
22
// TODO parameterize MatchHelper which will solve most of them
3+
/* eslint-disable no-continue */
34
import harden from '@endo/harden';
45
import {
56
q,
@@ -1321,12 +1322,13 @@ const makePatternKit = () => {
13211322
});
13221323

13231324
/**
1324-
* @param {CopyArray} elements
1325+
* @template {Passable} [T=Passable]
1326+
* @param {CopyArray<T>} elements
13251327
* @param {Pattern} elementPatt
13261328
* @param {bigint} bound Must be >= 1n
13271329
* @param {Rejector} reject
1328-
* @param {CopyArray} [inResults]
1329-
* @param {CopyArray} [outResults]
1330+
* @param {T[]} [inResults]
1331+
* @param {T[]} [outResults]
13301332
* @returns {boolean}
13311333
*/
13321334
const confirmElementsHasSplit = (
@@ -1337,7 +1339,7 @@ const makePatternKit = () => {
13371339
inResults = undefined,
13381340
outResults = undefined,
13391341
) => {
1340-
let count = 0n;
1342+
let inCount = 0n;
13411343
// Since this feature is motivated by ERTP's use on
13421344
// non-fungible (`set`, `copySet`) amounts,
13431345
// their arrays store their elements in decending lexicographic order.
@@ -1347,22 +1349,19 @@ const makePatternKit = () => {
13471349
// decending. Thus we iterate `elements` in reverse order.
13481350
for (let i = elements.length - 1; i >= 0; i -= 1) {
13491351
const element = elements[i];
1350-
if (count < bound) {
1351-
if (matches(element, elementPatt)) {
1352-
count += 1n;
1353-
if (inResults) inResults.push(element);
1354-
} else if (outResults) {
1355-
outResults.push(element);
1356-
}
1357-
} else if (outResults === undefined) {
1358-
break;
1359-
} else {
1352+
if (inCount >= bound) {
1353+
if (!outResults) break;
1354+
outResults.push(element);
1355+
} else if (matches(element, elementPatt)) {
1356+
inCount += 1n;
1357+
if (inResults) inResults.push(element);
1358+
} else if (outResults) {
13601359
outResults.push(element);
13611360
}
13621361
}
13631362
return (
1364-
count >= bound ||
1365-
(reject && reject`Has only ${q(count)} matches, but needs ${q(bound)}`)
1363+
inCount >= bound ||
1364+
(reject && reject`Has only ${q(inCount)} matches, but needs ${q(bound)}`)
13661365
);
13671366
};
13681367

@@ -1371,8 +1370,8 @@ const makePatternKit = () => {
13711370
* @param {Pattern} elementPatt
13721371
* @param {bigint} bound Must be >= 1n
13731372
* @param {Rejector} reject
1374-
* @param {CopyArray<[Key, bigint]>} [inResults]
1375-
* @param {CopyArray<[Key, bigint]>} [outResults]
1373+
* @param {[Key, bigint][]} [inResults]
1374+
* @param {[Key, bigint][]} [outResults]
13761375
* @returns {boolean}
13771376
*/
13781377
const pairsHasSplit = (
@@ -1383,7 +1382,7 @@ const makePatternKit = () => {
13831382
inResults = undefined,
13841383
outResults = undefined,
13851384
) => {
1386-
let count = 0n;
1385+
let inCount = 0n;
13871386
// Since this feature is motivated by ERTP's use on
13881387
// semi-fungible (`copyBag`) amounts,
13891388
// their arrays store their elements in decending lexicographic order.
@@ -1393,44 +1392,49 @@ const makePatternKit = () => {
13931392
// decending. Thus we iterate `pairs` in reverse order.
13941393
for (let i = pairs.length - 1; i >= 0; i -= 1) {
13951394
const [element, num] = pairs[i];
1396-
const numRest = bound - count;
1397-
if (numRest >= 1n) {
1398-
if (matches(element, elementPatt)) {
1399-
if (num <= numRest) {
1400-
count += num;
1401-
if (inResults) inResults.push([element, num]);
1402-
} else {
1403-
const numIn = numRest;
1404-
count += numIn;
1405-
if (inResults) inResults.push([element, numRest]);
1406-
if (outResults) outResults.push([element, num - numRest]);
1407-
}
1408-
} else if (outResults) {
1409-
outResults.push([element, num]);
1410-
}
1411-
} else if (outResults === undefined) {
1412-
break;
1413-
} else {
1395+
const stillNeeds = bound - inCount;
1396+
if (stillNeeds <= 0n) {
1397+
if (!outResults) break;
1398+
outResults.push([element, num]);
1399+
} else if (matches(element, elementPatt)) {
1400+
const isPartial = num > stillNeeds;
1401+
const numTake = isPartial ? stillNeeds : num;
1402+
inCount += numTake;
1403+
if (inResults) inResults.push([element, numTake]);
1404+
if (isPartial && outResults) outResults.push([element, num - numTake]);
1405+
} else if (outResults) {
14141406
outResults.push([element, num]);
14151407
}
14161408
}
14171409
return (
1418-
count >= bound ||
1419-
(reject && reject`Has only ${q(count)} matches, but needs ${q(bound)}`)
1410+
inCount >= bound ||
1411+
(reject && reject`Has only ${q(inCount)} matches, but needs ${q(bound)}`)
14201412
);
14211413
};
14221414

14231415
/**
1416+
* Confirms that `specimen` contains at least `bound` instances of an element
1417+
* matched by `elementPatt`, optionally returning those bounded matches and/or
1418+
* their complement as specified by `needInResults` and `needOutResults`
1419+
* (ensuring for CopyBags that at most one Key is split across both, but
1420+
* otherwise making no guarantee regarding the order in which elements are
1421+
* considered beyond a best-effort attempt to align with intuition).
1422+
* If the specimen does not contain enough matching instances, this function
1423+
* terminates as directed by `reject` (i.e., either returning `false` or
1424+
* throwing an error).
1425+
*
14241426
* @typedef {CopyArray | CopySet | CopyBag} Container
14251427
* @param {Container} specimen
14261428
* @param {Pattern} elementPatt
14271429
* @param {bigint} bound Must be >= 1n
14281430
* @param {Rejector} reject
1429-
* @param {boolean} [needInResults]
1430-
* @param {boolean} [needOutResults]
1431-
* @returns {[Container | undefined, Container | undefined] | false}
1431+
* @param {boolean} [needInResults] collect and return matches inside a
1432+
* container of the same shape as `specimen`
1433+
* @param {boolean} [needOutResults] collect and return rejects inside a
1434+
* container of the same shape as `specimen`
1435+
* @returns {[matches: Container | undefined, discards: Container | undefined] | false}
14321436
*/
1433-
const confirmContainerHasSplit = (
1437+
const containerHasSplit = (
14341438
specimen,
14351439
elementPatt,
14361440
bound,
@@ -1443,56 +1447,48 @@ const makePatternKit = () => {
14431447
const kind = kindOf(specimen);
14441448
switch (kind) {
14451449
case 'copyArray': {
1446-
if (
1447-
!confirmElementsHasSplit(
1450+
return (
1451+
confirmElementsHasSplit(
14481452
specimen,
14491453
elementPatt,
14501454
bound,
14511455
reject,
14521456
inResults,
14531457
outResults,
1454-
)
1455-
) {
1456-
// check logic already performed by confirmContainerHasSplit
1457-
return false;
1458-
}
1459-
return [inResults, outResults];
1458+
) && harden([inResults, outResults])
1459+
);
14601460
}
14611461
case 'copySet': {
1462-
if (
1463-
!confirmElementsHasSplit(
1462+
return (
1463+
confirmElementsHasSplit(
14641464
specimen.payload,
14651465
elementPatt,
14661466
bound,
14671467
reject,
14681468
inResults,
14691469
outResults,
1470-
)
1471-
) {
1472-
return false;
1473-
}
1474-
return [
1475-
inResults && makeCopySet(inResults),
1476-
outResults && makeCopySet(outResults),
1477-
];
1470+
) &&
1471+
harden([
1472+
inResults && makeCopySet(inResults),
1473+
outResults && makeCopySet(outResults),
1474+
])
1475+
);
14781476
}
14791477
case 'copyBag': {
1480-
if (
1481-
!pairsHasSplit(
1478+
return (
1479+
pairsHasSplit(
14821480
specimen.payload,
14831481
elementPatt,
14841482
bound,
14851483
reject,
14861484
inResults,
14871485
outResults,
1488-
)
1489-
) {
1490-
return false;
1491-
}
1492-
return [
1493-
inResults && makeCopyBag(inResults),
1494-
outResults && makeCopyBag(outResults),
1495-
];
1486+
) &&
1487+
harden([
1488+
inResults && makeCopyBag(inResults),
1489+
outResults && makeCopyBag(outResults),
1490+
])
1491+
);
14961492
}
14971493
default: {
14981494
return reject && reject`unexpected ${q(kind)}`;
@@ -1523,14 +1519,7 @@ const makePatternKit = () => {
15231519
) {
15241520
return false;
15251521
}
1526-
return !!confirmContainerHasSplit(
1527-
specimen,
1528-
elementPatt,
1529-
bound,
1530-
reject,
1531-
false,
1532-
false,
1533-
);
1522+
return !!containerHasSplit(specimen, elementPatt, bound, reject);
15341523
},
15351524

15361525
confirmIsWellFormed: (payload, reject) =>
@@ -2064,7 +2053,7 @@ const makePatternKit = () => {
20642053
getRankCover,
20652054
M,
20662055
kindOf,
2067-
containerHasSplit: confirmContainerHasSplit,
2056+
containerHasSplit,
20682057
});
20692058
};
20702059

0 commit comments

Comments
 (0)