Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patterns and Keys #4210

Merged
merged 1 commit into from
Jan 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 2 additions & 6 deletions packages/marshal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
isObject,
assertChecker,
getTag,
hasOwnPropertyOf,
} from './src/helpers/passStyle-helpers.js';

export { getErrorConstructor, toPassableError } from './src/helpers/error.js';
Expand All @@ -17,12 +18,7 @@ export {
passableSymbolForName,
} from './src/helpers/symbol.js';

export {
passStyleOf,
assertPassable,
everyPassableChild,
somePassableChild,
} from './src/passStyleOf.js';
export { passStyleOf, assertPassable } from './src/passStyleOf.js';

export { pureCopy, sameValueZero } from './src/pureCopy.js';
export { deeplyFulfilled } from './src/deeplyFulfilled.js';
Expand Down
16 changes: 4 additions & 12 deletions packages/marshal/src/assertPassStyleOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,11 @@ import { passStyleOf } from './passStyleOf.js';

const { details: X, quote: q } = assert;

/**
* @typedef {Array} CopyArray
*/

/**
* @typedef {Record<string, Passable>} CopyRecord
*/

/**
* Check whether the argument is a pass-by-copy array, AKA a "copyArray"
* in @agoric/marshal terms
*
* @param {CopyArray} array
* @param {CopyArray<any>} array
* @returns {boolean}
*/
const isCopyArray = array => passStyleOf(array) === 'copyArray';
Expand All @@ -26,7 +18,7 @@ harden(isCopyArray);
* Check whether the argument is a pass-by-copy record, AKA a
* "copyRecord" in @agoric/marshal terms
*
* @param {CopyRecord} record
* @param {CopyRecord<any>} record
* @returns {boolean}
*/
const isRecord = record => passStyleOf(record) === 'copyRecord';
Expand All @@ -45,7 +37,7 @@ harden(isRemotable);
* Assert that the argument is a pass-by-copy array, AKA a "copyArray"
* in @agoric/marshal terms
*
* @param {CopyArray} array
* @param {CopyArray<any>} array
* @param {string=} optNameOfArray
* @returns {void}
*/
Expand All @@ -64,7 +56,7 @@ harden(assertCopyArray);
* Assert that the argument is a pass-by-copy record, or a
* "copyRecord" in @agoric/marshal terms
*
* @param {CopyRecord} record
* @param {CopyRecord<any>} record
* @param {string=} optNameOfRecord
* @returns {void}
*/
Expand Down
7 changes: 1 addition & 6 deletions packages/marshal/src/helpers/copyArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ export const CopyArrayHelper = harden({
TypeError,
);
// Recursively validate that each member is passable.
CopyArrayHelper.every(candidate, v => !!passStyleOfRecur(v));
candidate.every(v => !!passStyleOfRecur(v));
},

every: (passable, fn) =>
// Note that we explicitly call `fn` with only the arguments we want
// to provide.
passable.every((v, i) => fn(v, i)),
});
8 changes: 1 addition & 7 deletions packages/marshal/src/helpers/copyRecord.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const { ownKeys } = Reflect;
const {
getPrototypeOf,
getOwnPropertyDescriptors,
entries,
prototype: objectPrototype,
} = Object;

Expand Down Expand Up @@ -62,11 +61,6 @@ export const CopyRecordHelper = harden({
checkNormalProperty(candidate, name, 'string', true, assertChecker);
}
// Recursively validate that each member is passable.
CopyRecordHelper.every(candidate, v => !!passStyleOfRecur(v));
Object.values(candidate).every(v => !!passStyleOfRecur(v));
},

every: (passable, fn) =>
// Note that we explicitly call `fn` with only the arguments we want
// to provide.
entries(passable).every(([k, v]) => fn(v, k)),
});
2 changes: 0 additions & 2 deletions packages/marshal/src/helpers/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ export const ErrorHelper = harden({
assertValid: candidate => {
ErrorHelper.canBeValid(candidate, assertChecker);
},

every: (_passable, _fn) => true,
});

/**
Expand Down
6 changes: 0 additions & 6 deletions packages/marshal/src/helpers/internal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,4 @@
* @property {(candidate: any,
* passStyleOfRecur: PassStyleOf
* ) => void} assertValid
*
* @property {(passable: Passable,
* fn: (passable: Passable, index: any) => boolean
* ) => boolean} every
* For recuring through the nested passable structure. Like
* `Array.prototype.every`, return `false` to stop early.
*/
4 changes: 1 addition & 3 deletions packages/marshal/src/helpers/tagged.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ export const TaggedHelper = harden({
checkNormalProperty(candidate, 'payload', 'string', true, assertChecker);

// Recursively validate that each member is passable.
TaggedHelper.every(candidate, v => !!passStyleOfRecur(v));
!!passStyleOfRecur(candidate.payload);
},

every: (passable, fn) => fn(passable.payload, 'payload'),
});
25 changes: 4 additions & 21 deletions packages/marshal/src/passStyleOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ const { isFrozen } = Object;
* does what it is supposed to do. `makePassStyleOf` is not trying to defend
* itself against malicious helpers, though it does defend against some
* accidents.
* @returns {{passStyleOf: PassStyleOf, HelperTable: any}}
* @returns {PassStyleOf}
*/
const makePassStyleOfKit = passStyleHelpers => {
const makePassStyleOf = passStyleHelpers => {
const HelperTable = {
__proto__: null,
copyArray: undefined,
Expand Down Expand Up @@ -179,35 +179,18 @@ const makePassStyleOfKit = passStyleHelpers => {

return passStyleOfRecur(passable);
};
return harden({ passStyleOf, HelperTable });
return harden(passStyleOf);
};

const { passStyleOf, HelperTable } = makePassStyleOfKit([
export const passStyleOf = makePassStyleOf([
CopyArrayHelper,
CopyRecordHelper,
TaggedHelper,
RemotableHelper,
ErrorHelper,
]);
export { passStyleOf };

export const assertPassable = val => {
passStyleOf(val); // throws if val is not a passable
};
harden(assertPassable);

export const everyPassableChild = (passable, fn) => {
const passStyle = passStyleOf(passable);
const helper = HelperTable[passStyle];
if (helper) {
// everyPassable guards .every so that each helper only gets a
// genuine passable of its own flavor.
return helper.every(passable, fn);
}
return true;
};
harden(everyPassableChild);

export const somePassableChild = (passable, fn) =>
!everyPassableChild(passable, (v, i) => !fn(v, i));
harden(somePassableChild);
10 changes: 10 additions & 0 deletions packages/marshal/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
* The leaves of a Passable's pass-by-copy superstructure.
*/

/**
* @template T
* @typedef {T[]} CopyArray
*/

/**
* @template T
* @typedef {Record<string, T>} CopyRecord
*/

/**
* @typedef {{
* [PASS_STYLE]: 'tagged',
Expand Down
10 changes: 8 additions & 2 deletions packages/store/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
export { isKey, assertKey } from './keys/checkKey.js';
export { keyLT, keyLTE, keyEQ, keyGTE, keyGT } from './keys/compareKeys.js';

export { makePatternKit } from './patterns/patternMatchers.js';
export { compareRank } from './patterns/rankOrder.js';
export {
M,
isPattern,
assertPattern,
matches,
fit,
} from './patterns/patternMatchers.js';
// export { compareRank } from './patterns/rankOrder.js';

export { makeScalarWeakSetStore } from './stores/scalarWeakSetStore.js';
export { makeScalarSetStore } from './stores/scalarSetStore.js';
Expand Down
10 changes: 6 additions & 4 deletions packages/store/src/keys/checkKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import {
assertChecker,
assertPassable,
everyPassableChild,
getTag,
isObject,
passStyleOf,
Expand Down Expand Up @@ -94,10 +93,13 @@ const checkKeyInternal = (val, check = x => x) => {

const passStyle = passStyleOf(val);
switch (passStyle) {
case 'copyRecord':
case 'copyRecord': {
// A copyRecord is a key iff all its children are keys
return Object.values(val).every(checkIt);
}
case 'copyArray': {
// A copyRecord or copyArray is a key iff all its children are keys
return everyPassableChild(val, checkIt);
// A copyArray is a key iff all its children are keys
return val.every(checkIt);
}
case 'tagged': {
const tag = getTag(val);
Expand Down
94 changes: 75 additions & 19 deletions packages/store/src/keys/copyMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
makeTagged,
passStyleOf,
} from '@agoric/marshal';
import { compareRank, sortByRank } from '../patterns/rankOrder.js';
import { compareAntiRank, sortByRank } from '../patterns/rankOrder.js';
import { checkCopySetKeys } from './copySet.js';

// eslint-disable-next-line spaced-comment
Expand All @@ -15,7 +15,7 @@ import { checkCopySetKeys } from './copySet.js';
const { details: X } = assert;
const { ownKeys } = Reflect;

/** @type WeakSet<CopyMap> */
/** @type WeakSet<CopyMap<any,any>> */
const copyMapMemo = new WeakSet();

/**
Expand Down Expand Up @@ -63,49 +63,105 @@ export const assertCopyMap = m => checkCopyMap(m, assertChecker);
harden(assertCopyMap);

/**
* @param {CopyMap} m
* @param {(v: Passable, i: number) => boolean} fn
* @returns {boolean}
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {K[]}
*/
export const everyCopyMapKey = (m, fn) => {
export const getCopyMapKeys = m => {
assertCopyMap(m);
return m.payload.keys.every((v, i) => fn(v, i));
return m.payload.keys;
};
harden(everyCopyMapKey);
harden(getCopyMapKeys);

/**
* @param {CopyMap} m
* @param {(v: Passable, i: number) => boolean} fn
* @returns {boolean}
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {V[]}
*/
export const everyCopyMapValue = (m, fn) => {
export const getCopyMapValues = m => {
assertCopyMap(m);
return m.payload.values.every((v, i) => fn(v, i));
return m.payload.values;
};
harden(getCopyMapValues);

/**
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {Iterable<[K,V]>}
*/
export const getCopyMapEntries = m => {
assertCopyMap(m);
const {
payload: { keys, values },
} = m;
const { length } = keys;
return harden({
[Symbol.iterator]: () => {
let i = 0;
return harden({
next: () => {
/** @type {IteratorResult<[K,V],void>} */
let result;
if (i < length) {
result = harden({ done: false, value: [keys[i], values[i]] });
i += 1;
return result;
} else {
result = harden({ done: true, value: undefined });
}
return result;
},
});
},
});
};
harden(getCopyMapEntries);

/**
* @template K,V
* @param {CopyMap<K,V>} m
* @param {(key: K, index: number) => boolean} fn
* @returns {boolean}
*/
export const everyCopyMapKey = (m, fn) =>
getCopyMapKeys(m).every((key, index) => fn(key, index));
harden(everyCopyMapKey);

/**
* @template K,V
* @param {CopyMap<K,V>} m
* @param {(value: V, index: number) => boolean} fn
* @returns {boolean}
*/
export const everyCopyMapValue = (m, fn) =>
getCopyMapValues(m).every((value, index) => fn(value, index));
harden(everyCopyMapValue);

/**
* @param {CopyMap} m
* @returns {CopySet}
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {CopySet<K>}
*/
export const copyMapKeySet = m =>
// A copyMap's keys are already in the internal form used by copySets.
makeTagged('copySet', m.payload.keys);
harden(copyMapKeySet);

/**
* @param {Iterable<[Passable, Passable]>} entries
* @returns {CopyMap}
* @template K,V
* @param {Iterable<[K, V]>} entries
* @returns {CopyMap<K,V>}
*/
export const makeCopyMap = entries => {
// This is weird, but reverse rank sorting the entries is a good first step
// for getting the rank sorted keys together with the values
// organized by those keys. Also, among values associated with
// keys in the same equivalence class, those are rank sorted. This
// keys in the same equivalence class, those are rank sorted.
// TODO This
// could solve the copyMap cover issue explained in patternMatchers.js.
// But only if we include this criteria in our validation of copyMaps,
// which we currently do not.
const sortedEntries = [...sortByRank(entries, compareRank)].reverse();
const sortedEntries = sortByRank(entries, compareAntiRank);
const keys = sortedEntries.map(([k, _v]) => k);
const values = sortedEntries.map(([_k, v]) => v);
return makeTagged('copyMap', { keys, values });
Expand Down
Loading