diff --git a/packages/exo/src/exo-tools.js b/packages/exo/src/exo-tools.js index ba0d9cfb63..8b7f3578be 100644 --- a/packages/exo/src/exo-tools.js +++ b/packages/exo/src/exo-tools.js @@ -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 */ @@ -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, @@ -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; @@ -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]; @@ -66,7 +76,7 @@ const desync = methodGuard => { } return { awaitIndexes, - rawMethodGuard: { + rawMethodGuardPayload: { argGuards: rawArgGuards.slice(0, argGuards.length), optionalArgGuards: rawArgGuards.slice(argGuards.length), restArgGuard, @@ -74,9 +84,9 @@ const desync = methodGuard => { }; }; -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) { @@ -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 => { @@ -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); } }; @@ -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); diff --git a/packages/patterns/src/patterns/internal-types.js b/packages/patterns/src/patterns/internal-types.js index c79f167136..c354f879b1 100644 --- a/packages/patterns/src/patterns/internal-types.js +++ b/packages/patterns/src/patterns/internal-types.js @@ -1 +1,67 @@ /// + +/** @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} CopyRecord */ +/** @template T @typedef {import('@endo/marshal').CopyArray} 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 + */ diff --git a/packages/patterns/src/patterns/patternMatchers.js b/packages/patterns/src/patterns/patternMatchers.js index 4ee1218280..79173f6e82 100644 --- a/packages/patterns/src/patterns/patternMatchers.js +++ b/packages/patterns/src/patterns/patternMatchers.js @@ -32,6 +32,8 @@ import { checkCopyBag, } from '../keys/checkKey.js'; +import './internal-types.js'; + /// const { quote: q, details: X, Fail } = assert; @@ -156,6 +158,18 @@ const makePatternKit = () => { // eslint-disable-next-line no-use-before-define HelpersByMatchTag[tag]; + /** + * Note that this function indicates absence by returning `undefined`, + * even though `undefined` is a valid pattern. To evade this confusion, + * to register a payload shape with that meaning, use `MM.undefined()`. + * + * @param {string} tag + * @returns {Pattern | undefined} + */ + const maybePayloadShape = tag => + // eslint-disable-next-line no-use-before-define + GuardPayloadShapes[tag]; + /** * @typedef {string} Kind * It is either a PassStyle other than 'tagged', or, if the underlying @@ -187,6 +201,12 @@ const makePatternKit = () => { // Buried here is the important case, where we process // the various patternNodes return matchHelper.checkIsWellFormed(tagged.payload, check); + } else { + const payloadShape = maybePayloadShape(tag); + if (payloadShape !== undefined) { + // eslint-disable-next-line no-use-before-define + return checkMatches(tagged.payload, payloadShape, check, tag); + } } switch (tag) { case 'copySet': { @@ -261,6 +281,16 @@ const makePatternKit = () => { return false; }; + /** + * Checks only recognized kinds, and only if the specimen + * passes the invariants associated with that recognition. + * + * @param {Passable} specimen + * @param {Kind} kind + * @returns {boolean} + */ + const isKind = (specimen, kind) => checkKind(specimen, kind, identChecker); + /** * @param {Passable} specimen * @param {Key} keyAsPattern @@ -493,8 +523,8 @@ const makePatternKit = () => { } const { payload: pattPayload } = patt; const { payload: specimenPayload } = specimen; - const pattKeySet = copyMapKeySet(pattPayload); - const specimenKeySet = copyMapKeySet(specimenPayload); + const pattKeySet = copyMapKeySet(patt); + const specimenKeySet = copyMapKeySet(specimen); // Compare keys as copySets if (!checkMatches(specimenKeySet, pattKeySet, check)) { return false; @@ -502,6 +532,11 @@ const makePatternKit = () => { const pattValues = pattPayload.values; const specimenValues = specimenPayload.values; // compare values as copyArrays + // TODO BUG: this assumes that the keys appear in the + // same order, so we can compare values in that order. + // However, we're only guaranteed that they appear in + // the same rankOrder. Thus we must search one of these + // in the other's rankOrder. return checkMatches(specimenValues, pattValues, check); } default: { @@ -651,8 +686,15 @@ const makePatternKit = () => { return getPassStyleCover(passStyle); }; + /** + * @param {Passable[]} array + * @param {Pattern} patt + * @param {Checker} check + * @param {string} [labelPrefix] + * @returns {boolean} + */ const arrayEveryMatchPattern = (array, patt, check, labelPrefix = '') => { - if (checkKind(patt, 'match:any', identChecker)) { + if (isKind(patt, 'match:any')) { // if the pattern is M.any(), we know its true return true; } @@ -709,7 +751,7 @@ const makePatternKit = () => { if ( patts.length === 2 && !matches(specimen, patts[0]) && - checkKind(patts[0], 'match:kind', identChecker) && + isKind(patts[0], 'match:kind') && patts[0].payload === 'undefined' ) { // Worth special casing the optional pattern for @@ -920,7 +962,7 @@ const makePatternKit = () => { const matchRemotableHelper = Far('match:remotable helper', { checkMatches: (specimen, remotableDesc, check) => { // Unfortunate duplication of checkKind logic, but no better choices. - if (checkKind(specimen, 'remotable', identChecker)) { + if (isKind(specimen, 'remotable')) { return true; } if (check === identChecker) { @@ -1527,53 +1569,6 @@ const makePatternKit = () => { return [base]; }; - /** - * @param {'sync'|'async'} callKind - * @param {ArgGuard[]} argGuards - * @param {ArgGuard[]} [optionalArgGuards] - * @param {ArgGuard} [restArgGuard] - * @returns {MethodGuardMaker} - */ - const makeMethodGuardMaker = ( - callKind, - argGuards, - optionalArgGuards = undefined, - restArgGuard = undefined, - ) => - harden({ - optional: (...optArgGuards) => { - optionalArgGuards === undefined || - Fail`Can only have one set of optional guards`; - restArgGuard === undefined || - Fail`optional arg guards must come before rest arg`; - return makeMethodGuardMaker(callKind, argGuards, optArgGuards); - }, - rest: rArgGuard => { - restArgGuard === undefined || Fail`Can only have one rest arg`; - return makeMethodGuardMaker( - callKind, - argGuards, - optionalArgGuards, - rArgGuard, - ); - }, - returns: (returnGuard = UndefinedShape) => - harden({ - klass: 'methodGuard', - callKind, - argGuards, - optionalArgGuards, - restArgGuard, - returnGuard, - }), - }); - - const makeAwaitArgGuard = argGuard => - harden({ - klass: 'awaitArg', - argGuard, - }); - // ////////////////// /** @type {MatcherNamespace} */ @@ -1662,22 +1657,18 @@ const makePatternKit = () => { eref: t => M.or(t, M.promise()), opt: t => M.or(M.undefined(), t), - interface: (interfaceName, methodGuards, { sloppy = false } = {}) => { - for (const [_, methodGuard] of entries(methodGuards)) { - methodGuard.klass === 'methodGuard' || - Fail`unrecognize method guard ${methodGuard}`; - } - return harden({ - klass: 'Interface', - interfaceName, - methodGuards, - sloppy, - }); - }, - call: (...argGuards) => makeMethodGuardMaker('sync', argGuards), - callWhen: (...argGuards) => makeMethodGuardMaker('async', argGuards), - - await: argGuard => makeAwaitArgGuard(argGuard), + interface: (interfaceName, methodGuards, options) => + // eslint-disable-next-line no-use-before-define + makeInterfaceGuard(interfaceName, methodGuards, options), + call: (...argGuards) => + // eslint-disable-next-line no-use-before-define + makeMethodGuardMaker('sync', argGuards), + callWhen: (...argGuards) => + // eslint-disable-next-line no-use-before-define + makeMethodGuardMaker('async', argGuards), + await: argGuard => + // eslint-disable-next-line no-use-before-define + makeAwaitArgGuard(argGuard), }); return harden({ @@ -1709,50 +1700,128 @@ export const { MM = M; -/** @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} CopyRecord */ -/** @template T @typedef {import('@endo/marshal').CopyArray} CopyArray */ -/** @typedef {import('@endo/marshal').Checker} Checker */ -/** @typedef {import('@endo/marshal').RankCompare} RankCompare */ -/** @typedef {import('@endo/marshal').RankCover} RankCover */ - -/** @typedef {import('../types').ArgGuard} ArgGuard */ -/** @typedef {import('../types').MethodGuardMaker} MethodGuardMaker */ -/** @typedef {import('../types').MatcherNamespace} MatcherNamespace */ -/** @typedef {import('../types').Key} Key */ -/** @typedef {import('../types').Pattern} Pattern */ -/** @typedef {import('../types').PatternKit} PatternKit */ -/** @typedef {import('../types').CheckPattern} CheckPattern */ -/** @typedef {import('../types').Limits} Limits */ -/** @typedef {import('../types').AllLimits} AllLimits */ -/** @typedef {import('../types').GetRankCover} GetRankCover */ +// //////////////////////////// Guards /////////////////////////////////////// + +const AwaitArgGuardPayloadShape = harden({ + argGuard: M.pattern(), +}); + +const AwaitArgGuardShape = M.kind('guard:awaitArgGuard'); /** - * @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 unchecker 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. + * @param {Pattern} argGuard + * @returns {AwaitArgGuard} + */ +const makeAwaitArgGuard = argGuard => { + const result = makeTagged('guard:awaitArgGuard', { + argGuard, + }); + mustMatch(result, AwaitArgGuardShape, 'argGuard'); + return result; +}; + +const PatternListShape = M.arrayOf(M.pattern()); + +const ArgGuardShape = M.or(M.pattern(), AwaitArgGuardShape); +const ArgGuardListShape = M.arrayOf(ArgGuardShape); + +const SyncMethodGuardPayloadShape = harden({ + callKind: 'sync', + argGuards: PatternListShape, + optionalArgGuards: M.opt(PatternListShape), + restArgGuard: M.pattern(), + returnGuard: M.pattern(), +}); + +const AsyncMethodGuardPayloadShape = harden({ + callKind: 'async', + argGuards: ArgGuardListShape, + optionalArgGuards: M.opt(ArgGuardListShape), + restArgGuard: M.pattern(), + returnGuard: M.pattern(), +}); + +const MethodGuardPayloadShape = M.or( + SyncMethodGuardPayloadShape, + AsyncMethodGuardPayloadShape, +); + +const MethodGuardShape = M.kind('guard:methodGuard'); + +/** + * @param {'sync'|'async'} callKind + * @param {ArgGuard[]} argGuards + * @param {ArgGuard[]} [optionalArgGuards] + * @param {ArgGuard} [restArgGuard] + * @returns {MethodGuardMaker0} */ +const makeMethodGuardMaker = ( + callKind, + argGuards, + optionalArgGuards = undefined, + restArgGuard = undefined, +) => + harden({ + optional: (...optArgGuards) => { + optionalArgGuards === undefined || + Fail`Can only have one set of optional guards`; + restArgGuard === undefined || + Fail`optional arg guards must come before rest arg`; + return makeMethodGuardMaker(callKind, argGuards, optArgGuards); + }, + rest: rArgGuard => { + restArgGuard === undefined || Fail`Can only have one rest arg`; + return makeMethodGuardMaker( + callKind, + argGuards, + optionalArgGuards, + rArgGuard, + ); + }, + returns: (returnGuard = M.undefined()) => { + const result = makeTagged('guard:methodGuard', { + callKind, + argGuards, + optionalArgGuards, + restArgGuard, + returnGuard, + }); + mustMatch(result, MethodGuardShape, 'methodGuard'); + return result; + }, + }); + +const InterfaceGuardPayloadShape = harden({ + interfaceName: M.string(), + methodGuards: M.recordOf(M.string(), MethodGuardShape), + sloppy: M.boolean(), +}); + +const InterfaceGuardShape = M.kind('guard:interfaceGuard'); + +/** + * @param {string} interfaceName + * @param {Record} methodGuards + * @param {{sloppy?: boolean}} [options] + * @returns {InterfaceGuard} + */ +const makeInterfaceGuard = (interfaceName, methodGuards, options = {}) => { + const { sloppy = false } = options; + for (const [_, methodGuard] of entries(methodGuards)) { + getTag(methodGuard) === 'guard:methodGuard' || + Fail`unrecognize method guard ${methodGuard}`; + } + const result = makeTagged('guard:interfaceGuard', { + interfaceName, + methodGuards, + sloppy, + }); + mustMatch(result, InterfaceGuardShape, 'interfaceGuard'); + return result; +}; + +const GuardPayloadShapes = harden({ + 'guard:awaitArgGuard': AwaitArgGuardPayloadShape, + 'guard:methodGuard': MethodGuardPayloadShape, + 'guard:interfaceGuard': InterfaceGuardPayloadShape, +}); diff --git a/packages/patterns/src/types.js b/packages/patterns/src/types.js index 7a4704a870..e2a0512eb3 100644 --- a/packages/patterns/src/types.js +++ b/packages/patterns/src/types.js @@ -483,13 +483,15 @@ export {}; * @property {>(interfaceName: string, * methodGuards: M, * options?: {sloppy?: boolean} - * ) => InterfaceGuard} interface Guard an interface to a far object or facet + * ) => InterfaceGuard} interface Guard the interface of an exo object * - * @property {(...argGuards: ArgGuard[]) => MethodGuardMaker} call Guard a synchronous call + * @property {(...argGuards: ArgGuard[]) => MethodGuardMaker0} call + * Guard a synchronous call * - * @property {(...argGuards: ArgGuard[]) => MethodGuardMaker} callWhen Guard an async call + * @property {(...argGuards: ArgGuard[]) => MethodGuardMaker0} callWhen + * Guard an async call * - * @property {(argGuard: ArgGuard) => ArgGuard} await Guard an await + * @property {(argGuard: ArgGuard) => AwaitArgGuard} await Guard an await */ /** @@ -500,16 +502,53 @@ export {}; // TODO parameterize this to match the behavior object it guards /** - * @typedef {{ - * klass: 'Interface', - * interfaceName: string, - * methodGuards: Record - * sloppy?: boolean + * @typedef {CopyTagged & { + * [Symbol.toStringTag]: 'guard:interfaceGuard', + * payload: { + * interfaceName: string, + * methodGuards: Record + * sloppy?: boolean + * } * }} InterfaceGuard */ /** - * @typedef {any} MethodGuardMaker + * @typedef {object} MethodGuardMaker0 + * A method name and parameter/return signature like: + * ```js + * foo(a, b, c = d, ...e) => f + * ``` + * should be guarded by something like: + * ```js + * { + * ...otherMethodGuards, + * foo: M.call(AShape, BShape).optional(CShape).rest(EShape).returns(FShape), + * } + * ``` + * @property {(...optArgGuards: ArgGuard[]) => MethodGuardMaker1} optional + * @property {(rArgGuard: Pattern) => MethodGuardMaker2} rest + * @property {(returnGuard?: Pattern) => MethodGuard} returns + */ + +/** + * @typedef {object} MethodGuardMaker1 + * A method name and parameter/return signature like: + * ```js + * foo(a, b, c = d, ...e) => f + * ``` + * should be guarded by something like: + * ```js + * { + * ...otherMethodGuards, + * foo: M.call(AShape, BShape).optional(CShape).rest(EShape).returns(FShape), + * } + * ``` + * @property {(rArgGuard: Pattern) => MethodGuardMaker2} rest + * @property {(returnGuard?: Pattern) => MethodGuard} returns + */ + +/** + * @typedef {object} MethodGuardMaker2 * A method name and parameter/return signature like: * ```js * foo(a, b, c = d, ...e) => f @@ -521,22 +560,29 @@ export {}; * foo: M.call(AShape, BShape).optional(CShape).rest(EShape).returns(FShape), * } * ``` + * @property {(returnGuard?: Pattern) => MethodGuard} returns */ -/** @typedef {{ klass: 'methodGuard', callKind: 'sync' | 'async', returnGuard: unknown }} MethodGuard */ -/** @typedef {any} ArgGuard */ +/** + * @typedef {CopyTagged & { + * [Symbol.toStringTag]: 'guard:methodGuard', + * payload: { + * callKind: 'sync' | 'async', + * argGuards: ArgGuard[] + * optionalArgGuards?: ArgGuard[] + * restArgGuard?: Pattern + * returnGuard?: Pattern + * } + * }} MethodGuard + */ /** - * @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 + * @typedef {CopyTagged & { + * [Symbol.toStringTag]: 'guard:awaitArgGuard' + * payload: { + * argGuard: Pattern + * } + * }} AwaitArgGuard */ + +/** @typedef {AwaitArgGuard | Pattern} ArgGuard */