Skip to content

Commit

Permalink
feat(defaultGuards): absorb sloppy and raw
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Oct 18, 2023
1 parent 5e1fa6c commit c9d9f50
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 37 deletions.
4 changes: 2 additions & 2 deletions packages/exo/src/exo-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export const defendPrototype = (
interfaceName,
methodGuards: mg,
symbolMethodGuards,
sloppy = false,
defaultGuards,
} = getInterfaceGuardPayload(interfaceGuard);
methodGuards = harden({
...mg,
Expand All @@ -322,7 +322,7 @@ export const defendPrototype = (
const unimplemented = listDifference(methodGuardNames, methodNames);
unimplemented.length === 0 ||
Fail`methods ${q(unimplemented)} not implemented by ${q(tag)}`;
if (!sloppy) {
if (defaultGuards === 'never') {
const unguarded = listDifference(methodNames, methodGuardNames);
unguarded.length === 0 ||
Fail`methods ${q(unguarded)} not guarded by ${q(interfaceName)}`;
Expand Down
21 changes: 11 additions & 10 deletions packages/patterns/src/patterns/patternMatchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -1794,14 +1794,14 @@ const makeAwaitArgGuard = argPattern => {

// M.rawValue()

const RawValueGuardPayloadShape = M.record();

// TODO does not need to be a singleton, and would not be if we added a
// parameter, like a description string.
/** @type {RawValueGuard} */
const TheRawValueGuard = harden({
klass: 'rawValueGuard',
});
const TheRawValueGuard = makeTagged('guard:rawValueGuard', {});

const RawValueGuardShape = TheRawValueGuard;
const RawValueGuardShape = M.kind('guard:rawValueGuard');

export const isRawValueGuard = specimen =>
matches(specimen, RawValueGuardShape);
Expand Down Expand Up @@ -1917,10 +1917,10 @@ const InterfaceGuardPayloadShape = M.splitRecord(
{
interfaceName: M.string(),
methodGuards: M.recordOf(M.string(), MethodGuardShape),
sloppy: M.boolean(),
raw: M.boolean(),
defaultGuards: M.or('never', 'passable', 'raw'),
},
{
sloppy: M.boolean(),
symbolMethodGuards: M.mapOf(M.symbol(), MethodGuardShape),
},
);
Expand Down Expand Up @@ -1972,11 +1972,12 @@ harden(getInterfaceMethodKeys);
* @template {Record<PropertyKey, MethodGuard>} [M = Record<PropertyKey, MethodGuard>]
* @param {string} interfaceName
* @param {M} methodGuards
* @param {{ sloppy?: boolean, raw?: boolean }} [options]
* @param {{ sloppy?: boolean, defaultGuards?: import('../types.js').DefaultGuardType }} [options]
* @returns {InterfaceGuard<M>}
*/
const makeInterfaceGuard = (interfaceName, methodGuards, options = {}) => {
const { sloppy = false, raw = false } = options;
const { sloppy = false, defaultGuards = sloppy ? 'passable' : 'never' } =
options;
// For backwards compatibility, string-keyed method guards are represented in
// a CopyRecord. But symbol-keyed methods cannot be, so we put those in a
// CopyMap when present.
Expand All @@ -1999,15 +2000,15 @@ const makeInterfaceGuard = (interfaceName, methodGuards, options = {}) => {
...(symbolMethodGuardsEntries.length
? { symbolMethodGuards: makeCopyMap(symbolMethodGuardsEntries) }
: {}),
sloppy,
raw,
defaultGuards,
});
assertInterfaceGuard(result);
return /** @type {InterfaceGuard<M>} */ (result);
};

const GuardPayloadShapes = harden({
'guard:awaitArgGuard': AwaitArgGuardPayloadShape,
'guard:rawValueGuard': RawValueGuardPayloadShape,
'guard:methodGuard': MethodGuardPayloadShape,
'guard:interfaceGuard': InterfaceGuardPayloadShape,
});
45 changes: 20 additions & 25 deletions packages/patterns/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,21 +456,32 @@ export {};
* Matches any Passable that is matched by `subPatt` or is the exact value `undefined`.
*/

/**
* @typedef {'never' | 'passable' | 'raw'} DefaultGuardType
*/

/**
* @typedef {<M extends Record<PropertyKey, MethodGuard>>(
* interfaceName: string,
* methodGuards: M,
* options: {defaultGuards?: 'never', sloppy?: false }) => InterfaceGuard<M>
* } MakeInterfaceGuardStrict
*/
/**
* @typedef {(
* interfaceName: string,
* methodGuards: any,
* options: {sloppy: true}) => InterfaceGuard<Record<PropertyKey, MethodGuard>>
* options: {defaultGuards?: 'passable' | 'raw', sloppy?: true }) => InterfaceGuard<Record<PropertyKey, MethodGuard>>
* } MakeInterfaceGuardSloppy
*/
/**
* @typedef {<M extends Record<PropertyKey, MethodGuard>>(
* interfaceName: string,
* methodGuards: M,
* options?: {sloppy?: boolean}) => InterfaceGuard<M>
* options?: {defaultGuards?: DefaultGuardType, sloppy?: boolean}) => InterfaceGuard<M>
* } MakeInterfaceGuardGeneral
*/
/** @typedef {MakeInterfaceGuardSloppy & MakeInterfaceGuardGeneral} MakeInterfaceGuard */
/** @typedef {MakeInterfaceGuardStrict & MakeInterfaceGuardSloppy & MakeInterfaceGuardGeneral} MakeInterfaceGuard */

/**
* @typedef {object} GuardMakers
Expand Down Expand Up @@ -500,20 +511,10 @@ export {};
* Any `AwaitArgGuard` may not appear as a rest pattern or a result pattern,
* only a top-level single parameter pattern.
*
* HAZARD: Until https://github.com/endojs/endo/pull/1712 an `AwaitArgGuard`
* is itself a `CopyRecord`. If used nested within a pattern, rather than
* at top level, it may be mistaken for a CopyRecord pattern that would match
* only a specimen shaped like an `AwaitArgGuard`.
*
* @property {(() => RawValueGuard)} rawValue
* In parameter position, pass this argument through without any checking.
* In rest position, pass the rest of the arguments through without any checking.
* In return position, return the result without any checking.
*
* HAZARD: Until https://github.com/endojs/endo/pull/1712 a `RawValueGuard`
* is itself a `CopyRecord`. If used nested within a pattern, rather than
* at top level, it may be mistaken for a CopyRecord pattern that would match
* only a specimen shaped like a `RawValueGuard`.
*/

/**
Expand All @@ -530,11 +531,8 @@ export {};
* Omit<T, symbol> & Partial<{ [K in Extract<keyof T, symbol>]: never }>,
* symbolMethodGuards?:
* CopyMap<Extract<keyof T, symbol>, T[Extract<keyof T, symbol>]>,
* sloppy?: boolean,
* raw?: boolean,
* defaultGuards: DefaultGuardType,
* }} InterfaceGuardPayload
*
* At most one of `sloppy` or `raw` can be true.
*/

/**
Expand Down Expand Up @@ -622,14 +620,11 @@ export {};
*/

/**
* @typedef {{
* klass: 'rawValueGuard'
* }} RawValueGuard
*
* TODO https://github.com/endojs/endo/pull/1712 to make it into a genuine
* guard that is distinct from a copyRecord.
* Unlike InterfaceGuard or MethodGuard, for RawValueGuard it is a correctness
* issue, so that the guard not be mistaken for the copyRecord as key/pattern.
* @typedef {{}} RawValueGuardPayload
*/

/**
* @typedef {CopyTagged<'guard:rawValueGuard', RawValueGuardPayload>} RawValueGuard
*/

/** @typedef {RawValueGuard | Pattern} SyncValueGuard */
Expand Down

0 comments on commit c9d9f50

Please sign in to comment.