Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5c8b0d9
fix(patterns)!: Account for the compactOrdered "~" prefix when comput…
gibson042 Jan 13, 2026
7e18395
refactor(patterns): Simplify some match helper `getRankCover` impleme…
gibson042 Jan 13, 2026
374edcf
feat(patterns): Improve some `getRankCover` implementations
gibson042 Jan 13, 2026
add869d
feat(patterns): Tighten bounds for `getRankCover([key, ..., nonKey, .…
gibson042 Jan 13, 2026
ebed59f
test(patterns): Add property-based testing for getRankCover
gibson042 Jan 16, 2026
7796183
fix(patterns): Don't let Object.prototype pollute the collection of m…
gibson042 Jan 16, 2026
03a9dd1
fix(patterns): Don't confuse "copyBag" as a pass style
gibson042 Jan 16, 2026
17178d7
feat(patterns): Further tighten bounds for `getRankCover([key, ..., n…
gibson042 Jan 16, 2026
8ccff21
fixup! test(patterns): Add property-based testing for getRankCover
gibson042 Jan 16, 2026
794e79a
fixup! fix(patterns)!: Account for the compactOrdered "~" prefix when…
gibson042 Jan 20, 2026
d118f35
fixup! feat(patterns): Further tighten bounds for `getRankCover([key,…
gibson042 Jan 20, 2026
aaae55e
test(patterns): Limite generated the size of generated arrays
gibson042 Jan 20, 2026
332961f
fixup! test(patterns): Add property-based testing for getRankCover
gibson042 Jan 20, 2026
5c83d8b
fixup! test(patterns): Add property-based testing for getRankCover
gibson042 Jan 20, 2026
ca12b12
fixup! fix(patterns): Don't confuse "copyBag" as a pass style
gibson042 Jan 23, 2026
4a911fc
test(patterns): Use fast-check more efficiently
gibson042 Feb 6, 2026
31c8b54
fixup! fix(patterns): Don't confuse "copyBag" as a pass style
gibson042 Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ catalogs:
eslint-plugin-eslint-comments: ^3.2.0
eslint-plugin-import: ^2.31.0
typescript: ~5.9.2
"@fast-check/ava": ^1.1.5
"@fast-check/ava": ^2.0.2
enableScripts: false
nodeLinker: node-modules
5 changes: 5 additions & 0 deletions packages/marshal/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
User-visible changes in `@endo/marshal`:

# Next release

- `FullRankCover` is deprecated. Instead, use `provideStaticRanks(encodePassable)['*'].cover`.
- `getPassStyleCover(passStyle)` is deprecated. Instead, use `provideStaticRanks(encodePassable)[passStyle].cover`.

# 1.8.0 (2025-07-11)

- Introduces an environment variable config option `ENDO_RANK_STRINGS` to change the rank ordering of strings from the current (incorrect) ordering by UTF-16 code unit used by JavaScript's `<` and `.sort()` operations to (correct and OCapN-conformant) ordering by Unicode code point. It currently defaults to "utf16-code-unit-order", matching the previously-unconditional behavior.
Expand Down
1 change: 1 addition & 0 deletions packages/marshal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export {
compareAntiRank,
makeFullOrderComparatorKit,
getPassStyleCover,
provideStaticRanks,
intersectRankCovers,
unionRankCovers,
} from './src/rankOrder.js';
Expand Down
1 change: 1 addition & 0 deletions packages/marshal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@endo/env-options": "workspace:^",
"@endo/errors": "workspace:^",
"@endo/eventual-send": "workspace:^",
"@endo/memoize": "workspace:^",
"@endo/nat": "workspace:^",
"@endo/pass-style": "workspace:^",
"@endo/promise-kit": "workspace:^"
Expand Down
47 changes: 44 additions & 3 deletions packages/marshal/src/rankOrder.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { objectMap } from '@endo/common/object-map.js';
import { getEnvironmentOption as getenv } from '@endo/env-options';
import { Fail, q } from '@endo/errors';
import { memoize } from '@endo/memoize';
import { getTag, passStyleOf, nameForPassableSymbol } from '@endo/pass-style';
import {
passStylePrefixes,
Expand Down Expand Up @@ -116,6 +118,7 @@ harden(compareNumerics);

/**
* @typedef {Record<PassStyle, { index: number, cover: RankCover }>} PassStyleRanksRecord
* @typedef {PassStyleRanksRecord & { '*': { cover: RankCover } }} StaticRanksRecord
*/

const passStyleRanks = /** @type {PassStyleRanksRecord} */ (
Expand Down Expand Up @@ -156,10 +159,42 @@ harden(passStyleRanks);
*
* @param {PassStyle} passStyle
* @returns {RankCover}
* @deprecated Coverage depends upon format; use {@link provideStaticRanks}
* instead.
*/
export const getPassStyleCover = passStyle => passStyleRanks[passStyle].cover;
harden(getPassStyleCover);

// Use singleton null as a sentinel for detecting an encodePassable output
// prefix (e.g., the "~" that starts compactOrdered output).
const { null: nullTypePrefix } = passStylePrefixes;
nullTypePrefix.length === 1 ||
Fail`internal: null pass style prefix must be a single character, not ${q(nullTypePrefix)}`;

export const provideStaticRanks = memoize(
/** @type {(encodePassable: ((p: Passable) => string)) => StaticRanksRecord} */
encodePassable => {
const encodedNull = encodePassable(null);
encodedNull.endsWith(nullTypePrefix) ||
Fail`expected null encoding ${q(nullTypePrefix)} not found in ${q(encodedNull)}`;
const prefix = encodedNull.slice(0, -nullTypePrefix.length);
return harden({
__proto__: null,
...objectMap(passStyleRanks, ({ index, cover }) => {
const [lower, upper] = cover;
const prefixedCover = /** @type {RankCover} */ ([
`${prefix}${lower}`,
`${prefix}${upper}`,
]);
return { index, cover: prefixedCover };
}),
// The full upper bound ends with `{` because `|` is reserved to exceed
// all pass style prefixes; @see {@link passStylePrefixes}.
'*': { cover: [prefix, `${prefix}{`] },
});
},
);

/**
* @type {WeakMap<RankCompare,WeakSet<Passable[]>>}
*/
Expand Down Expand Up @@ -474,7 +509,11 @@ export const getIndexCover = (sorted, compare, [leftKey, rightKey]) => {
};
harden(getIndexCover);

/** @type {RankCover} */
/**
* @type {RankCover}
* @deprecated Coverage depends upon format; use
* `provideStaticRanks(encodePassable)['*'].cover` instead.
*/
export const FullRankCover = harden(['', '{']);

/**
Expand Down Expand Up @@ -528,6 +567,7 @@ const minRank = (compare, a, b) => (compare(a, b) <= 0 ? a : b);
* @returns {RankCover}
*/
export const unionRankCovers = (compare, covers) => {
covers.length > 0 || Fail`Cannot union empty covers`;
/**
* @param {RankCover} a
* @param {RankCover} b
Expand All @@ -537,7 +577,7 @@ export const unionRankCovers = (compare, covers) => {
minRank(compare, leftA, leftB),
maxRank(compare, rightA, rightB),
];
return covers.reduce(unionRankCoverPair, ['{', '']);
return covers.reduce(unionRankCoverPair);
};
harden(unionRankCovers);

Expand All @@ -547,6 +587,7 @@ harden(unionRankCovers);
* @returns {RankCover}
*/
export const intersectRankCovers = (compare, covers) => {
covers.length > 0 || Fail`Cannot intersect empty covers`;
/**
* @param {RankCover} a
* @param {RankCover} b
Expand All @@ -556,7 +597,7 @@ export const intersectRankCovers = (compare, covers) => {
maxRank(compare, leftA, leftB),
minRank(compare, rightA, rightB),
];
return covers.reduce(intersectRankCoverPair, ['', '{']);
return covers.reduce(intersectRankCoverPair);
};
harden(intersectRankCovers);

Expand Down
4 changes: 3 additions & 1 deletion packages/marshal/test/encodePassable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import {
import { compareRank, makeFullOrderComparatorKit } from '../src/rankOrder.js';
import { unsortedSample } from '../tools/marshal-test-data.js';

const { arbPassable } = makeArbitraries(fc, ['byteArray']);
const { arbPassable } = makeArbitraries(fc, {
excludePassStyles: ['byteArray'],
});

const statelessEncodePassableLegacy = makeEncodePassable();

Expand Down
Loading