Skip to content

Commit

Permalink
fix(patterns): tag and type guards
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Jun 6, 2023
1 parent fdf5365 commit 91d36e7
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 161 deletions.
57 changes: 32 additions & 25 deletions packages/exo/src/exo-tools.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { E, Far } from '@endo/far';
import { listDifference, objectMap, mustMatch, M } from '@endo/patterns';
import {
listDifference,
objectMap,
mustMatch,
M,
matches,
} from '@endo/patterns';

/** @typedef {import('@endo/patterns').Method} Method */
/** @typedef {import('@endo/patterns').MethodGuard} MethodGuard */
Expand All @@ -17,8 +23,8 @@ const { defineProperties } = Object;
*/
const MinMethodGuard = M.call().rest(M.any()).returns(M.any());

const defendSyncArgs = (args, methodGuard, label) => {
const { argGuards, optionalArgGuards, restArgGuard } = methodGuard;
const defendSyncArgs = (args, methodGuardPayload, label) => {
const { argGuards, optionalArgGuards, restArgGuard } = methodGuardPayload;
const paramsPattern = M.splitArray(
argGuards,
optionalArgGuards,
Expand All @@ -29,16 +35,16 @@ const defendSyncArgs = (args, methodGuard, label) => {

/**
* @param {Method} method
* @param {MethodGuard} methodGuard
* @param {MethodGuard['payload']} methodGuardPayload
* @param {string} label
* @returns {Method}
*/
const defendSyncMethod = (method, methodGuard, label) => {
const { returnGuard } = methodGuard;
const defendSyncMethod = (method, methodGuardPayload, label) => {
const { returnGuard } = methodGuardPayload;
const { syncMethod } = {
// Note purposeful use of `this` and concise method syntax
syncMethod(...args) {
defendSyncArgs(harden(args), methodGuard, label);
defendSyncArgs(harden(args), methodGuardPayload, label);
const result = apply(method, this, args);
mustMatch(harden(result), returnGuard, `${label}: result`);
return result;
Expand All @@ -48,10 +54,14 @@ const defendSyncMethod = (method, methodGuard, label) => {
};

const isAwaitArgGuard = argGuard =>
argGuard && typeof argGuard === 'object' && argGuard.klass === 'awaitArg';
matches(argGuard, M.kind('guard:awaitArgGuard'));

const desync = methodGuard => {
const { argGuards, optionalArgGuards = [], restArgGuard } = methodGuard;
const desync = methodGuardPayload => {
const {
argGuards,
optionalArgGuards = [],
restArgGuard,
} = methodGuardPayload;
!isAwaitArgGuard(restArgGuard) ||
Fail`Rest args may not be awaited: ${restArgGuard}`;
const rawArgGuards = [...argGuards, ...optionalArgGuards];
Expand All @@ -66,17 +76,17 @@ const desync = methodGuard => {
}
return {
awaitIndexes,
rawMethodGuard: {
rawMethodGuardPayload: {
argGuards: rawArgGuards.slice(0, argGuards.length),
optionalArgGuards: rawArgGuards.slice(argGuards.length),
restArgGuard,
},
};
};

const defendAsyncMethod = (method, methodGuard, label) => {
const { returnGuard } = methodGuard;
const { awaitIndexes, rawMethodGuard } = desync(methodGuard);
const defendAsyncMethod = (method, methodGuardPayload, label) => {
const { returnGuard } = methodGuardPayload;
const { awaitIndexes, rawMethodGuardPayload } = desync(methodGuardPayload);
const { asyncMethod } = {
// Note purposeful use of `this` and concise method syntax
asyncMethod(...args) {
Expand All @@ -87,7 +97,7 @@ const defendAsyncMethod = (method, methodGuard, label) => {
for (let j = 0; j < awaitIndexes.length; j += 1) {
rawArgs[awaitIndexes[j]] = awaitedArgs[j];
}
defendSyncArgs(rawArgs, rawMethodGuard, label);
defendSyncArgs(rawArgs, rawMethodGuardPayload, label);
return apply(method, this, rawArgs);
});
return E.when(resultP, result => {
Expand All @@ -106,13 +116,14 @@ const defendAsyncMethod = (method, methodGuard, label) => {
* @param {string} label
*/
const defendMethod = (method, methodGuard, label) => {
const { klass, callKind } = methodGuard;
assert(klass === 'methodGuard');
mustMatch(methodGuard, M.kind('guard:methodGuard'), 'internal');
const { payload } = methodGuard;
const { callKind } = payload;
if (callKind === 'sync') {
return defendSyncMethod(method, methodGuard, label);
return defendSyncMethod(method, payload, label);
} else {
assert(callKind === 'async');
return defendAsyncMethod(method, methodGuard, label);
return defendAsyncMethod(method, payload, label);
}
};

Expand Down Expand Up @@ -228,15 +239,11 @@ export const defendPrototype = (
);
let methodGuards;
if (interfaceGuard) {
mustMatch(interfaceGuard, M.kind('guard:interfaceGuard'), 'internal');
const {
klass,
interfaceName,
methodGuards: mg,
sloppy = false,
payload: { interfaceName, methodGuards: mg, sloppy = false },
} = interfaceGuard;
methodGuards = mg;
assert.equal(klass, 'Interface');
assert.typeof(interfaceName, 'string');
{
const methodGuardNames = ownKeys(methodGuards);
const unimplemented = listDifference(methodGuardNames, methodNames);
Expand Down
66 changes: 66 additions & 0 deletions packages/patterns/src/patterns/internal-types.js
Original file line number Diff line number Diff line change
@@ -1 +1,67 @@
/// <reference types="ses"/>

/** @typedef {import('@endo/marshal').Passable} Passable */
/** @typedef {import('@endo/marshal').PassStyle} PassStyle */
/** @typedef {import('@endo/marshal').CopyTagged} CopyTagged */
/** @template T @typedef {import('@endo/marshal').CopyRecord<T>} CopyRecord */
/** @template T @typedef {import('@endo/marshal').CopyArray<T>} CopyArray */
/** @typedef {import('@endo/marshal').Checker} Checker */
/** @typedef {import('@endo/marshal').RankCompare} RankCompare */
/** @typedef {import('@endo/marshal').RankCover} RankCover */

/** @typedef {import('../types.js').AwaitArgGuard} AwaitArgGuard */
/** @typedef {import('../types.js').ArgGuard} ArgGuard */
/** @typedef {import('../types.js').MethodGuard} MethodGuard */
/** @typedef {import('../types.js').InterfaceGuard} InterfaceGuard */
/** @typedef {import('../types.js').MethodGuardMaker0} MethodGuardMaker0 */

/** @typedef {import('../types').MatcherNamespace} MatcherNamespace */
/** @typedef {import('../types').Key} Key */
/** @typedef {import('../types').Pattern} Pattern */
/** @typedef {import('../types').CheckPattern} CheckPattern */
/** @typedef {import('../types').Limits} Limits */
/** @typedef {import('../types').AllLimits} AllLimits */
/** @typedef {import('../types').GetRankCover} GetRankCover */

/**
* @typedef {object} MatchHelper
* This factors out only the parts specific to each kind of Matcher. It is
* encapsulated, and its methods can make the stated unchecked assumptions
* enforced by the common calling logic.
*
* @property {(allegedPayload: Passable,
* check: Checker
* ) => boolean} checkIsWellFormed
* Reports whether `allegedPayload` is valid as the payload of a CopyTagged
* whose tag corresponds with this MatchHelper's Matchers.
*
* @property {(specimen: Passable,
* matcherPayload: Passable,
* check: Checker,
* ) => boolean} checkMatches
* Assuming validity of `matcherPayload` as the payload of a Matcher corresponding
* with this MatchHelper, reports whether `specimen` is matched by that Matcher.
*
* @property {import('../types').GetRankCover} getRankCover
* Assumes this is the payload of a CopyTagged with the corresponding
* matchTag. Return a RankCover to bound from below and above,
* in rank order, all possible Passables that would match this Matcher.
* The left element must be before or the same rank as any possible
* matching specimen. The right element must be after or the same
* rank as any possible matching specimen.
*/

/**
* @typedef {object} PatternKit
* @property {(specimen: Passable,
* patt: Passable,
* check: Checker,
* label?: string|number
* ) => boolean} checkMatches
* @property {(specimen: Passable, patt: Pattern) => boolean} matches
* @property {(specimen: Passable, patt: Pattern, label?: string|number) => void} mustMatch
* @property {(patt: Pattern) => void} assertPattern
* @property {(patt: Passable) => boolean} isPattern
* @property {GetRankCover} getRankCover
* @property {MatcherNamespace} M
*/
Loading

0 comments on commit 91d36e7

Please sign in to comment.