From e6d6984fbd28b068f2af4cc6d20555dd8160f58a Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 9 Jan 2024 11:51:33 -0800 Subject: [PATCH 1/6] test: isFake type --- packages/pass-style/test/test-passStyleOf.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/pass-style/test/test-passStyleOf.js b/packages/pass-style/test/test-passStyleOf.js index 77dd715be1..29771a0a70 100644 --- a/packages/pass-style/test/test-passStyleOf.js +++ b/packages/pass-style/test/test-passStyleOf.js @@ -6,6 +6,11 @@ import { Far } from '../src/make-far.js'; import { makeTagged } from '../src/makeTagged.js'; import { PASS_STYLE } from '../src/passStyle-helpers.js'; +const harden = /** @type {import('ses').Harden & { isFake?: boolean }} */ ( + // eslint-disable-next-line no-undef + global.harden +); + const { quote: q } = assert; const { getPrototypeOf, defineProperty } = Object; const { ownKeys } = Reflect; @@ -102,6 +107,7 @@ test('some passStyleOf rejections', t => { * * @param {string} [tag] * @param {object|null} [proto] + * @returns {{ [PASS_STYLE]: 'remotable', [Symbol.toStringTag]: string }} */ const makeTagishRecord = (tag = 'Remotable', proto = undefined) => { return Object.create(proto === undefined ? Object.prototype : proto, { @@ -177,12 +183,14 @@ test('passStyleOf testing remotables', t => { t.is(passStyleOf(Far('foo', () => 'far function')), 'remotable'); const tagRecord1 = harden(makeTagishRecord('Alleged: manually constructed')); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const farObj1 = harden({ __proto__: tagRecord1, }); t.is(passStyleOf(farObj1), 'remotable'); const tagRecord2 = makeTagishRecord('Alleged: tagRecord not hardened'); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const farObj2 = Object.freeze({ __proto__: tagRecord2, }); @@ -198,12 +206,14 @@ test('passStyleOf testing remotables', t => { const tagRecord3 = Object.freeze( makeTagishRecord('Alleged: both manually frozen'), ); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const farObj3 = Object.freeze({ __proto__: tagRecord3, }); t.is(passStyleOf(farObj3), 'remotable'); const tagRecord4 = harden(makeTagishRecord('Remotable')); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const farObj4 = harden({ __proto__: tagRecord4, }); @@ -222,6 +232,7 @@ test('passStyleOf testing remotables', t => { const farObjProto6 = harden({ __proto__: tagRecord6, }); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const farObj6 = harden({ __proto__: farObjProto6, }); @@ -249,6 +260,7 @@ test('passStyleOf testing remotables', t => { const farTagRecord7 = getPrototypeOf(farBaseProto7); t.is(farTagRecord7[PASS_STYLE], 'remotable'); t.is(getPrototypeOf(farTagRecord7), Object.prototype); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const farObj7 = new FarBaseClass7(3); t.is(passStyleOf(farObj7), 'remotable'); t.is(farObj7.add(7), 10); @@ -260,6 +272,7 @@ test('passStyleOf testing remotables', t => { } } harden(FarSubclass8); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const farObj8 = new FarSubclass8(3); t.is(passStyleOf(farObj8), 'remotable'); t.is(farObj8.twice(), 14); @@ -405,6 +418,7 @@ test('remotables - safety from the gibson042 attack', t => { test('Unexpected stack on errors', t => { let err; try { + // @ts-expect-error purposeful type violation for testing null.error; } catch (e) { err = e; @@ -427,11 +441,13 @@ test('Allow toStringTag overrides', t => { t.is(`${alice}`, '[object DebugName: Allison]'); t.is(`${q(alice)}`, '"[DebugName: Allison]"'); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const carol = harden({ __proto__: alice }); t.is(passStyleOf(carol), 'remotable'); t.is(`${carol}`, '[object DebugName: Allison]'); t.is(`${q(carol)}`, '"[DebugName: Allison]"'); + /** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */ const bob = harden({ __proto__: carol, [Symbol.toStringTag]: 'DebugName: Robert', @@ -444,7 +460,9 @@ test('Allow toStringTag overrides', t => { t.is(fred.name, 'fred'); defineProperty(fred, Symbol.toStringTag, { value: 'DebugName: Friedrich' }); const f = Far('Fred', fred); + // @ts-expect-error TS doesn't know `fred` has changed t.is(f, fred); + // @ts-expect-error TS doesn't know `fred` has changed t.is(passStyleOf(fred), 'remotable'); t.is(`${fred}`, '() => {}'); t.is(Object.prototype.toString.call(fred), '[object DebugName: Friedrich]'); From 935718bb6179c4b7295ac62920b77b6de899dcb6 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 9 Jan 2024 10:35:22 -0800 Subject: [PATCH 2/6] chore(types): type makeTagged --- packages/pass-style/src/makeTagged.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/pass-style/src/makeTagged.js b/packages/pass-style/src/makeTagged.js index b20ee544b8..b4e6c08c2e 100644 --- a/packages/pass-style/src/makeTagged.js +++ b/packages/pass-style/src/makeTagged.js @@ -6,6 +6,13 @@ import { assertPassable } from './passStyleOf.js'; const { create, prototype: objectPrototype } = Object; const { Fail } = assert; +/** + * @template {string} T + * @template {any} P + * @param {T} tag + * @param {P} payload + * @returns {import('./types.js').CopyTagged} + */ export const makeTagged = (tag, payload) => { typeof tag === 'string' || Fail`The tag of a tagged record must be a string: ${tag}`; From ae6ad156e43fafb11df394f901df372760f9cbcc Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Mon, 8 Jan 2024 13:46:23 -0800 Subject: [PATCH 3/6] feat(types): generic Passable --- packages/marshal/src/deeplyFulfilled.js | 4 + packages/marshal/src/encodePassable.js | 4 +- packages/marshal/src/rankOrder.js | 14 +- packages/pass-style/.eslintignore | 2 + packages/pass-style/package.json | 1 + packages/pass-style/src/make-far.js | 10 +- packages/pass-style/src/makeTagged.js | 2 +- packages/pass-style/src/passStyle-helpers.js | 8 +- packages/pass-style/src/passStyleOf.js | 18 +- packages/pass-style/src/remotable.js | 14 +- packages/pass-style/src/typeGuards.js | 17 +- packages/pass-style/src/types.d.ts | 203 ++++++++++++++++++ packages/pass-style/src/types.js | 164 +------------- packages/pass-style/src/types.test-d.ts | 31 +++ packages/pass-style/test/test-passStyleOf.js | 5 + packages/pass-style/tools/arb-passable.js | 7 +- .../patterns/src/patterns/patternMatchers.js | 2 + 17 files changed, 306 insertions(+), 200 deletions(-) create mode 100644 packages/pass-style/.eslintignore create mode 100644 packages/pass-style/src/types.d.ts create mode 100644 packages/pass-style/src/types.test-d.ts diff --git a/packages/marshal/src/deeplyFulfilled.js b/packages/marshal/src/deeplyFulfilled.js index ebedc93a6d..eda563da37 100644 --- a/packages/marshal/src/deeplyFulfilled.js +++ b/packages/marshal/src/deeplyFulfilled.js @@ -51,18 +51,22 @@ export const deeplyFulfilled = async val => { const passStyle = passStyleOf(val); switch (passStyle) { case 'copyRecord': { + // @ts-expect-error FIXME narrowed const names = ownKeys(val); + // @ts-expect-error FIXME narrowed const valPs = names.map(name => deeplyFulfilled(val[name])); return E.when(Promise.all(valPs), vals => harden(fromEntries(vals.map((c, i) => [names[i], c]))), ); } case 'copyArray': { + // @ts-expect-error FIXME narrowed const valPs = val.map(p => deeplyFulfilled(p)); return E.when(Promise.all(valPs), vals => harden(vals)); } case 'tagged': { const tag = getTag(val); + // @ts-expect-error FIXME narrowed return E.when(deeplyFulfilled(val.payload), payload => makeTagged(tag, payload), ); diff --git a/packages/marshal/src/encodePassable.js b/packages/marshal/src/encodePassable.js index 387a88b402..002d3be0f7 100644 --- a/packages/marshal/src/encodePassable.js +++ b/packages/marshal/src/encodePassable.js @@ -27,7 +27,7 @@ const { ownKeys } = Reflect; * string-named own properties. `recordNames` returns those name *reverse* * sorted, because that's how records are compared, encoded, and sorted. * - * @template T + * @template {Passable} T * @param {CopyRecord} record * @returns {string[]} */ @@ -44,7 +44,7 @@ harden(recordNames); * Assuming that `record` is a CopyRecord and `names` is `recordNames(record)`, * return the corresponding array of property values. * - * @template T + * @template {Passable} T * @param {CopyRecord} record * @param {string[]} names * @returns {T[]} diff --git a/packages/marshal/src/rankOrder.js b/packages/marshal/src/rankOrder.js index f8d8d9d95c..cdeea95af4 100644 --- a/packages/marshal/src/rankOrder.js +++ b/packages/marshal/src/rankOrder.js @@ -382,18 +382,20 @@ export const coveredEntries = (sorted, [leftIndex, rightIndex]) => { harden(coveredEntries); /** + * @template {Passable} T * @param {RankCompare} compare - * @param {Passable} a - * @param {Passable} b - * @returns {Passable} + * @param {T} a + * @param {T} b + * @returns {T} */ const maxRank = (compare, a, b) => (compare(a, b) >= 0 ? a : b); /** + * @template {Passable} T * @param {RankCompare} compare - * @param {Passable} a - * @param {Passable} b - * @returns {Passable} + * @param {T} a + * @param {T} b + * @returns {T} */ const minRank = (compare, a, b) => (compare(a, b) <= 0 ? a : b); diff --git a/packages/pass-style/.eslintignore b/packages/pass-style/.eslintignore new file mode 100644 index 0000000000..d76bcff161 --- /dev/null +++ b/packages/pass-style/.eslintignore @@ -0,0 +1,2 @@ +# typescript-eslint errors on this because it has no typecheck information, because tsc produced it for the `types.d.ts` instead +/src/types.js diff --git a/packages/pass-style/package.json b/packages/pass-style/package.json index ecd9cd9602..0ef01e72f3 100644 --- a/packages/pass-style/package.json +++ b/packages/pass-style/package.json @@ -18,6 +18,7 @@ "module": "./index.js", "exports": { ".": "./index.js", + "./src/types.js": "./src/types.js", "./tools.js": "./tools.js", "./endow.js": "./endow.js", "./package.json": "./package.json" diff --git a/packages/pass-style/src/make-far.js b/packages/pass-style/src/make-far.js index 1e92acbbc3..2bbc912794 100644 --- a/packages/pass-style/src/make-far.js +++ b/packages/pass-style/src/make-far.js @@ -5,7 +5,7 @@ import { assertChecker, PASS_STYLE } from './passStyle-helpers.js'; import { assertIface, getInterfaceOf, RemotableHelper } from './remotable.js'; /** @typedef {import('./types.js').InterfaceSpec} InterfaceSpec */ -/** @template L,R @typedef {import('@endo/eventual-send').RemotableBrand} RemotableBrand */ +/** @template L,R @typedef {import('@endo/eventual-send/src/types').RemotableBrand} RemotableBrand */ const { quote: q, Fail } = assert; @@ -62,7 +62,8 @@ const assertCanBeRemotable = candidate => * // https://github.com/Agoric/agoric-sdk/issues/804 * * @template {{}} T - * @param {InterfaceSpec} [iface] The interface specification for + * @template {InterfaceSpec} I + * @param {I} [iface] The interface specification for * the remotable. For now, a string iface must be "Remotable" or begin with * "Alleged: " or "DebugName: ", to serve as the alleged name. More * general ifaces are not yet implemented. This is temporary. We include the @@ -75,9 +76,10 @@ const assertCanBeRemotable = candidate => * @param {undefined} [props] Currently may only be undefined. * That plan is that own-properties are copied to the remotable * @param {T} [remotable] The object used as the remotable - * @returns {T & RemotableBrand<{}, T>} remotable, modified for debuggability + * @returns {T & import('./types.js').RemotableObject & RemotableBrand<{}, T>}} remotable, modified for debuggability */ export const Remotable = ( + // @ts-expect-error I could have different subtype than string iface = 'Remotable', props = undefined, remotable = /** @type {T} */ ({}), @@ -125,7 +127,7 @@ export const Remotable = ( // COMMITTED! // We're committed, so keep the interface for future reference. assert(iface !== undefined); // To make TypeScript happy - return /** @type {T & RemotableBrand<{}, T>} */ (remotable); + return /** @type {any} */ (remotable); }; harden(Remotable); diff --git a/packages/pass-style/src/makeTagged.js b/packages/pass-style/src/makeTagged.js index b4e6c08c2e..90f5ffad5f 100644 --- a/packages/pass-style/src/makeTagged.js +++ b/packages/pass-style/src/makeTagged.js @@ -8,7 +8,7 @@ const { Fail } = assert; /** * @template {string} T - * @template {any} P + * @template {import('./types.js').Passable} P * @param {T} tag * @param {P} payload * @returns {import('./types.js').CopyTagged} diff --git a/packages/pass-style/src/passStyle-helpers.js b/packages/pass-style/src/passStyle-helpers.js index 840156641c..4afb6e0efb 100644 --- a/packages/pass-style/src/passStyle-helpers.js +++ b/packages/pass-style/src/passStyle-helpers.js @@ -29,6 +29,7 @@ export const hasOwnPropertyOf = (obj, prop) => apply(objectHasOwnProperty, obj, [prop]); harden(hasOwnPropertyOf); +/** @type {(val) => val is {}} */ export const isObject = val => Object(val) === val; harden(isObject); @@ -122,6 +123,11 @@ export const checkNormalProperty = ( }; harden(checkNormalProperty); +/** + * @template {import('./types.js').InterfaceSpec} T + * @param {import('./types.js').TaggedRecord} tagRecord + * @returns {T} + */ export const getTag = tagRecord => tagRecord[Symbol.toStringTag]; harden(getTag); @@ -138,7 +144,7 @@ harden(checkPassStyle); const makeCheckTagRecord = checkProto => { /** - * @param {{ [PASS_STYLE]: string }} tagRecord + * @param {import('./types.js').TaggedRecord} tagRecord * @param {PassStyle} passStyle * @param {Checker} [check] * @returns {boolean} diff --git a/packages/pass-style/src/passStyleOf.js b/packages/pass-style/src/passStyleOf.js index 0a49fe71a1..f635d47a21 100644 --- a/packages/pass-style/src/passStyleOf.js +++ b/packages/pass-style/src/passStyleOf.js @@ -81,28 +81,26 @@ const makePassStyleOf = passStyleHelpers => { * structures, so without this cache, these algorithms could be * O(N**2) or worse. * - * @type {WeakMap} + * @type {WeakMap} */ const passStyleMemo = new WeakMap(); /** * @type {PassStyleOf} */ + // @ts-expect-error cast const passStyleOf = passable => { // Even when a WeakSet is correct, when the set has a shorter lifetime // than its keys, we prefer a Set due to expected implementation // tradeoffs. const inProgress = new Set(); - /** - * @type {PassStyleOf} - */ const passStyleOfRecur = inner => { const innerIsObject = isObject(inner); if (innerIsObject) { - if (passStyleMemo.has(inner)) { - // @ts-ignore TypeScript doesn't know that `get` after `has` is safe - return passStyleMemo.get(inner); + const innerMemo = passStyleMemo.get(inner); + if (innerMemo) { + return innerMemo; } !inProgress.has(inner) || Fail`Pass-by-copy data cannot be cyclic ${inner}`; @@ -117,9 +115,6 @@ const makePassStyleOf = passStyleHelpers => { return passStyle; }; - /** - * @type {PassStyleOf} - */ const passStyleOfInternal = inner => { const typestr = typeof inner; switch (typestr) { @@ -164,10 +159,12 @@ const makePassStyleOf = passStyleHelpers => { } for (const helper of passStyleHelpers) { if (helper.canBeValid(inner)) { + // @ts-expect-error XXX helper.assertValid(inner, passStyleOfRecur); return helper.styleName; } } + // @ts-expect-error XXX remotableHelper.assertValid(inner, passStyleOfRecur); return 'remotable'; } @@ -176,6 +173,7 @@ const makePassStyleOf = passStyleHelpers => { Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`; typeof inner.then !== 'function' || Fail`Cannot pass non-promise thenables`; + // @ts-expect-error XXX remotableHelper.assertValid(inner, passStyleOfRecur); return 'remotable'; } diff --git a/packages/pass-style/src/remotable.js b/packages/pass-style/src/remotable.js index 476dff5387..8d4add0968 100644 --- a/packages/pass-style/src/remotable.js +++ b/packages/pass-style/src/remotable.js @@ -13,7 +13,6 @@ import { /** @typedef {import('./types.js').Checker} Checker */ /** @typedef {import('./types.js').InterfaceSpec} InterfaceSpec */ -/** @typedef {import('./types.js').MarshalGetInterfaceOf} MarshalGetInterfaceOf */ /** @typedef {import('./internal-types.js').PassStyleHelper} PassStyleHelper */ /** @typedef {import('./types.js').RemotableObject} Remotable */ @@ -167,15 +166,26 @@ const checkRemotable = (val, check) => { return result; }; -/** @type {MarshalGetInterfaceOf} */ +/** + * Simple semantics, just tell what interface (or undefined) a remotable has. + * @type {{ + * (val: import('./types.js').TaggedRecord): T; + * (val: any): string | undefined; + * }} + * @returns the interface specification, or undefined + * if not a deemed to be a Remotable + */ export const getInterfaceOf = val => { if ( !isObject(val) || val[PASS_STYLE] !== 'remotable' || + // @ts-expect-error FIXME !checkRemotable(val) ) { + // @ts-expect-error FIXME return undefined; } + // @ts-expect-error FIXME return getTag(val); }; harden(getInterfaceOf); diff --git a/packages/pass-style/src/typeGuards.js b/packages/pass-style/src/typeGuards.js index 2d2f13e267..ffc6e7c398 100644 --- a/packages/pass-style/src/typeGuards.js +++ b/packages/pass-style/src/typeGuards.js @@ -17,7 +17,7 @@ const { Fail, quote: q } = assert; * Check whether the argument is a pass-by-copy array, AKA a "copyArray" * in @endo/marshal terms * - * @param {Passable} arr + * @param {any} arr * @returns {arr is CopyArray} */ const isCopyArray = arr => passStyleOf(arr) === 'copyArray'; @@ -27,7 +27,7 @@ harden(isCopyArray); * Check whether the argument is a pass-by-copy record, AKA a * "copyRecord" in @endo/marshal terms * - * @param {Passable} record + * @param {any} record * @returns {record is CopyRecord} */ const isRecord = record => passStyleOf(record) === 'copyRecord'; @@ -43,13 +43,10 @@ const isRemotable = remotable => passStyleOf(remotable) === 'remotable'; harden(isRemotable); /** - * @callback AssertArray - * @param {Passable} array + * @param {any} array * @param {string=} optNameOfArray * @returns {asserts array is CopyArray} */ - -/** @type {AssertArray} */ const assertCopyArray = (array, optNameOfArray = 'Alleged array') => { const passStyle = passStyleOf(array); passStyle === 'copyArray' || @@ -60,13 +57,10 @@ const assertCopyArray = (array, optNameOfArray = 'Alleged array') => { harden(assertCopyArray); /** - * @callback AssertRecord - * @param {Passable} record + * @param {any} record * @param {string=} optNameOfRecord * @returns {asserts record is CopyRecord} */ - -/** @type {AssertRecord} */ const assertRecord = (record, optNameOfRecord = 'Alleged record') => { const passStyle = passStyleOf(record); passStyle === 'copyRecord' || @@ -77,13 +71,10 @@ const assertRecord = (record, optNameOfRecord = 'Alleged record') => { harden(assertRecord); /** - * @callback AssertRemotable * @param {Passable} remotable * @param {string=} optNameOfRemotable * @returns {asserts remotable is Remotable} */ - -/** @type {AssertRemotable} */ const assertRemotable = ( remotable, optNameOfRemotable = 'Alleged remotable', diff --git a/packages/pass-style/src/types.d.ts b/packages/pass-style/src/types.d.ts new file mode 100644 index 0000000000..040865c05d --- /dev/null +++ b/packages/pass-style/src/types.d.ts @@ -0,0 +1,203 @@ +/* eslint-disable no-use-before-define */ +import { PASS_STYLE } from './passStyle-helpers.js'; + +/** + * Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). + */ +export type Primitive = + | null + | undefined + | string + | number + | boolean + | symbol + | bigint; + +export type PrimitiveStyle = + | 'undefined' + | 'null' + | 'boolean' + | 'number' + | 'bigint' + | 'string' + | 'symbol'; + +export type ContainerStyle = 'copyRecord' | 'copyArray' | 'tagged'; + +export type PassStyle = + | PrimitiveStyle + | ContainerStyle + | 'remotable' + | 'error' + | 'promise'; + +export type TaggedOrRemotable = 'tagged' | 'remotable'; + +export type PassStyled = { + [PASS_STYLE]: S; +}; + +export type ExtractStyle

> = P[typeof PASS_STYLE]; + +export type PassByCopy = + | Primitive + | Error + | CopyArray + | CopyRecord + | CopyTagged; + +export type PassByRef = + | RemotableObject + | Promise + | Promise; + +/** + * A Passable is acyclic data that can be marshalled. It must be hardened to + * remain + * stable (even if some components are proxies; see PureData restriction below), + * and is classified by PassStyle: + * * Atomic primitive values have a PrimitiveStyle (PassStyle + * 'undefined' | 'null' | 'boolean' | 'number' | 'bigint' + * | 'string' | 'symbol'). + * * Containers aggregate other Passables into + * * sequences as CopyArrays (PassStyle 'copyArray'), or + * * string-keyed dictionaries as CopyRecords (PassStyle 'copyRecord'), or + * * higher-level types as CopyTaggeds (PassStyle 'tagged'). + * * PassableCaps (PassStyle 'remotable' | 'promise') expose local values to + * remote interaction. + * * As a special case to support system observability, error objects are + * Passable (PassStyle 'error'). + * + * A Passable is essentially a pass-by-copy superstructure with a + * pass-by-reference + * exit point at the site of each PassableCap (which marshalling represents + * using 'slots'). + */ +export type Passable< + PC extends PassableCap = PassableCap, + E extends Error = Error, +> = Primitive | Container | PC | E; + +export type Container = + | CopyArrayI + | CopyRecordI + | CopyTaggedI; +interface CopyArrayI + extends CopyArray> {} +interface CopyRecordI + extends CopyRecord> {} +interface CopyTaggedI + extends CopyTagged> {} + +export type PassStyleOf = { + (p: undefined): 'undefined'; + (p: string): 'string'; + (p: boolean): 'boolean'; + (p: number): 'number'; + (p: bigint): 'bigint'; + (p: symbol): 'symbol'; + (p: null): 'null'; + (p: Promise): 'promise'; + (p: Error): 'error'; + (p: CopyTagged): 'tagged'; + (p: any[]): 'copyArray'; + (p: Iterable): 'remotable'; + (p: Iterator): 'remotable'; + >(p: T): ExtractStyle; + (p: { [key: string]: any }): 'copyRecord'; + (p: any): PassStyle; +}; +/** + * A Passable is PureData when its entire data structure is free of PassableCaps + * (remotables and promises) and error objects. + * PureData is an arbitrary composition of primitive values into CopyArray + * and/or + * CopyRecord and/or CopyTagged containers (or a single primitive value with no + * container), and is fully pass-by-copy. + * + * This restriction assures absence of side effects and interleaving risks *given* + * that none of the containers can be a Proxy instance. + * TODO SECURITY BUG we plan to enforce this, giving PureData the same security + * properties as the proposed + * [Records and Tuples](https://github.com/tc39/proposal-record-tuple). + * + * Given this (currently counter-factual) assumption, a PureData value cannot + * be used as a communications channel, + * and can therefore be safely shared with subgraphs that should not be able + * to communicate with each other. + * Without that assumption, such a guarantee requires a marshal-unmarshal round + * trip (as exists between vats) to produce data structures disconnected from + * any potential proxies. + */ +export type PureData = Passable; +export type TaggedRecord< + S extends TaggedOrRemotable, + I extends InterfaceSpec, +> = PassStyled & { + [Symbol.toStringTag]: I; +}; +/** + * An object marked as remotely accessible using the `Far` or `Remotable` + * functions, or a local presence representing such a remote object. + * + * A more natural name would be Remotable, but that could be confused with the + * value of the `Remotable` export of this module (a function). + */ +export type RemotableObject = TaggedRecord< + 'remotable', + I +>; +/** + * The authority-bearing leaves of a Passable's pass-by-copy superstructure. + */ +export type PassableCap = Promise | RemotableObject; +/** + * A Passable sequence of Passable values. + */ +export type CopyArray = Array; + +/** + * A Passable dictionary in which each key is a string and each value is Passable. + */ +export type CopyRecord = Record; +/** + * A Passable "tagged record" with semantics specific to the tag identified in + * the `[Symbol.toStringTag]` property (such as 'copySet', 'copyBag', + * or 'copyMap'). + * It must have a property with key equal to the `PASS_STYLE` export and + * value 'tagged' + * and no other properties except `[Symbol.toStringTag]` and `payload`. + */ +export type CopyTagged< + Tag extends string = string, + Payload extends Passable = any, +> = TaggedRecord<'tagged', Tag> & { + payload: Payload; +}; +/** + * This is an interface specification. + * For now, it is just a string, but we retain the option to make it `PureData`. + * Either way, it must remain pure, so that it can be safely shared by subgraphs + * that are not supposed to be able to communicate. + */ +export type InterfaceSpec = string; +/** + * Internal to a useful pattern for writing checking logic + * (a "checkFoo" function) that can be used to implement a predicate + * (an "isFoo" function) or a validator (an "assertFoo" function). + * + * * A predicate ideally only returns `true` or `false` and rarely throws. + * * A validator throws an informative diagnostic when the predicate + * would have returned `false`, and simply returns `undefined` normally + * when the predicate would have returned `true`. + * * The internal checking function that they share is parameterized by a + * `Checker` that determines how to proceed with a failure condition. + * Predicates pass in an identity function as checker. Validators + * pass in `assertChecker` which is a trivial wrapper around `assert`. + * + * See the various uses for good examples. + */ +export type Checker = ( + cond: boolean, + details?: import('ses').Details | undefined, +) => boolean; diff --git a/packages/pass-style/src/types.js b/packages/pass-style/src/types.js index 438d053605..407ac0b20e 100644 --- a/packages/pass-style/src/types.js +++ b/packages/pass-style/src/types.js @@ -1,161 +1,5 @@ -export {}; - -/** - * @typedef { 'undefined' | 'null' | - * 'boolean' | 'number' | 'bigint' | 'string' | 'symbol' - * } PrimitiveStyle - */ - -/** - * @typedef { PrimitiveStyle | - * 'copyRecord' | 'copyArray' | 'tagged' | - * 'remotable' | - * 'error' | 'promise' - * } PassStyle - */ - -// TODO declare more precise types throughout this file, so the type system -// and IDE can be more helpful. - -/** - * @typedef {any} Passable - * - * A Passable is acyclic data that can be marshalled. It must be hardened to - * remain - * stable (even if some components are proxies; see PureData restriction below), - * and is classified by PassStyle: - * * Atomic primitive values have a PrimitiveStyle (PassStyle - * 'undefined' | 'null' | 'boolean' | 'number' | 'bigint' - * | 'string' | 'symbol'). - * * Containers aggregate other Passables into - * * sequences as CopyArrays (PassStyle 'copyArray'), or - * * string-keyed dictionaries as CopyRecords (PassStyle 'copyRecord'), or - * * higher-level types as CopyTaggeds (PassStyle 'tagged'). - * * PassableCaps (PassStyle 'remotable' | 'promise') expose local values to - * remote interaction. - * * As a special case to support system observability, error objects are - * Passable (PassStyle 'error'). - * - * A Passable is essentially a pass-by-copy superstructure with a - * pass-by-reference - * exit point at the site of each PassableCap (which marshalling represents - * using 'slots'). - */ - -/** - * @callback PassStyleOf - * @param {Passable} passable - * @returns {PassStyle} - */ - -/** - * @typedef {Passable} PureData - * - * A Passable is PureData when its entire data structure is free of PassableCaps - * (remotables and promises) and error objects. - * PureData is an arbitrary composition of primitive values into CopyArray - * and/or - * CopyRecord and/or CopyTagged containers (or a single primitive value with no - * container), and is fully pass-by-copy. - * - * This restriction assures absence of side effects and interleaving risks *given* - * that none of the containers can be a Proxy instance. - * TODO SECURITY BUG we plan to enforce this, giving PureData the same security - * properties as the proposed - * [Records and Tuples](https://github.com/tc39/proposal-record-tuple). - * - * Given this (currently counter-factual) assumption, a PureData value cannot - * be used as a communications channel, - * and can therefore be safely shared with subgraphs that should not be able - * to communicate with each other. - * Without that assumption, such a guarantee requires a marshal-unmarshal round - * trip (as exists between vats) to produce data structures disconnected from - * any potential proxies. - */ - -/** - * @typedef {Passable} RemotableObject - * - * An object marked as remotely accessible using the `Far` or `Remotable` - * functions, or a local presence representing such a remote object. - */ +/** @file Empty twin for .d.ts */ +/* eslint-disable */ +import { PASS_STYLE } from './passStyle-helpers.js'; -/** - * @typedef {Promise | RemotableObject} PassableCap - * - * The authority-bearing leaves of a Passable's pass-by-copy superstructure. - */ - -/** - * @template {Passable} [T=Passable] - * @typedef {T[]} CopyArray - * - * A Passable sequence of Passable values. - */ - -/** - * @template {Passable} [T=Passable] - * @typedef {Record} CopyRecord - * - * A Passable dictionary in which each key is a string and each value is Passable. - */ - -/** - * @template {string} [Tag=string] - * @template {Passable} [Payload=Passable] - * @typedef {{ - * [Symbol.toStringTag]: Tag, - * payload: Payload, - * [passStyle: symbol]: 'tagged' | string, - * }} CopyTagged - * - * A Passable "tagged record" with semantics specific to the tag identified in - * the `[Symbol.toStringTag]` property (such as 'copySet', 'copyBag', - * or 'copyMap'). - * It must have a property with key equal to the `PASS_STYLE` export and - * value 'tagged' - * and no other properties except `[Symbol.toStringTag]` and `payload`. - * - * TODO - * But TypeScript complains about a declaration like `[PASS_STYLE]: 'tagged'` - * because importing packages do not know what `PASS_STYLE` is, - * so we appease it with a looser but less accurate definition - * using symbol index properties and `| string`. - */ - -/** - * @typedef {string} InterfaceSpec - * This is an interface specification. - * For now, it is just a string, but will eventually be `PureData`. Either - * way, it must remain pure, so that it can be safely shared by subgraphs that - * are not supposed to be able to communicate. - */ - -/** - * @callback MarshalGetInterfaceOf - * Simple semantics, just tell what interface (or undefined) a remotable has. - * @param {any} maybeRemotable the value to check - * @returns {InterfaceSpec|undefined} the interface specification, or undefined - * if not a deemed to be a Remotable - */ - -/** - * @callback Checker - * Internal to a useful pattern for writing checking logic - * (a "checkFoo" function) that can be used to implement a predicate - * (an "isFoo" function) or a validator (an "assertFoo" function). - * - * * A predicate ideally only returns `true` or `false` and rarely throws. - * * A validator throws an informative diagnostic when the predicate - * would have returned `false`, and simply returns `undefined` normally - * when the predicate would have returned `true`. - * * The internal checking function that they share is parameterized by a - * `Checker` that determines how to proceed with a failure condition. - * Predicates pass in an identity function as checker. Validators - * pass in `assertChecker` which is a trivial wrapper around `assert`. - * - * See the various uses for good examples. - * @param {boolean} cond - * @param {import('ses').Details} [details] - * @returns {boolean} - */ +export {}; diff --git a/packages/pass-style/src/types.test-d.ts b/packages/pass-style/src/types.test-d.ts new file mode 100644 index 0000000000..957f15ff59 --- /dev/null +++ b/packages/pass-style/src/types.test-d.ts @@ -0,0 +1,31 @@ +/* eslint-disable */ +import { expectType, expectNotType } from 'tsd'; +import { Far } from './make-far'; +import { passStyleOf } from './passStyleOf'; +import { makeTagged } from './makeTagged'; +import { CopyTagged, PassStyle } from './types'; +import { PASS_STYLE } from './passStyle-helpers'; + +const remotable = Far('foo', {}); + +const copyTagged = makeTagged('someTag', remotable); +expectType>(copyTagged); + +const someUnknown: unknown = null; + +expectType<'undefined'>(passStyleOf(undefined)); +expectType<'string'>(passStyleOf('str')); +expectType<'boolean'>(passStyleOf(true)); +expectType<'number'>(passStyleOf(1)); +expectType<'bigint'>(passStyleOf(1n)); +expectType<'symbol'>(passStyleOf(Symbol.for('foo'))); +expectType<'null'>(passStyleOf(null)); +expectType<'promise'>(passStyleOf(Promise.resolve())); +expectType<'error'>(passStyleOf(new Error())); +expectType<'tagged'>(passStyleOf(copyTagged)); +expectType<'copyArray'>(passStyleOf([])); +expectType<'copyRecord'>(passStyleOf({})); +// though the object is specifying a PASS_STYLE, it doesn't match the case for extracting it +expectType<'copyRecord'>(passStyleOf({ [PASS_STYLE]: 'arbitrary' } as const)); +expectType<'remotable'>(passStyleOf(remotable)); +expectType(passStyleOf(someUnknown)); diff --git a/packages/pass-style/test/test-passStyleOf.js b/packages/pass-style/test/test-passStyleOf.js index 29771a0a70..5c86478484 100644 --- a/packages/pass-style/test/test-passStyleOf.js +++ b/packages/pass-style/test/test-passStyleOf.js @@ -161,6 +161,11 @@ test('passStyleOf testing tagged records', t => { value: { [PASS_STYLE]: 0 }, message: '0 must be a string', }, + { + label: 'unrecognized', + value: { [PASS_STYLE]: 'arbitrary' }, + message: 'Unrecognized PassStyle: "arbitrary"', + }, ]; for (const testCase of tagRecordBadPayloads) { const { label, message, ...desc } = testCase; diff --git a/packages/pass-style/tools/arb-passable.js b/packages/pass-style/tools/arb-passable.js index 1d6651cdec..22549f5d83 100644 --- a/packages/pass-style/tools/arb-passable.js +++ b/packages/pass-style/tools/arb-passable.js @@ -99,7 +99,12 @@ const { arbDag } = fc.letrec(tie => { ), }), ) - .map(({ type, payload }) => makeTagged(type, payload)), + .map(({ type, payload }) => + makeTagged( + type, + /** @type {import('../src/types.js').Passable} */ (payload), + ), + ), ), }; }); diff --git a/packages/patterns/src/patterns/patternMatchers.js b/packages/patterns/src/patterns/patternMatchers.js index 054d79679f..c492b2d97e 100644 --- a/packages/patterns/src/patterns/patternMatchers.js +++ b/packages/patterns/src/patterns/patternMatchers.js @@ -1,3 +1,5 @@ +// @ts-nocheck So many errors that the suppressions hamper readability. +// TODO parameterize MatchHelper which will solve most of them import { assertChecker, Far, From 81aced167a707f83b628033ba846672c810c5b3a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 9 Jan 2024 22:29:30 -0600 Subject: [PATCH 4/6] chore(patterns): define Key using parameterized Passable chore(patterns): define Key using parameterized Passable --- packages/patterns/src/types.js | 3 ++- packages/patterns/test/test-patterns.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/patterns/src/types.js b/packages/patterns/src/types.js index 5b7ff5577e..0318c81c59 100644 --- a/packages/patterns/src/types.js +++ b/packages/patterns/src/types.js @@ -4,6 +4,7 @@ export {}; /** @typedef {import('@endo/pass-style').Passable} Passable */ /** @typedef {import('@endo/pass-style').PassStyle} PassStyle */ +/** @typedef {import('@endo/pass-style').RemotableObject} RemotableObject */ /** * @template {string} [Tag=string] * @template {Passable} [Payload=Passable] @@ -22,7 +23,7 @@ export {}; /** @typedef {import('@endo/marshal').RankCover} RankCover */ /** - * @typedef {Passable} Key + * @typedef {import('@endo/pass-style').Passable} Key * * Keys are Passable arbitrarily-nested pass-by-copy containers * (CopyArray, CopyRecord, CopySet, CopyBag, CopyMap) in which every diff --git a/packages/patterns/test/test-patterns.js b/packages/patterns/test/test-patterns.js index 60a9891da1..031213b9f4 100644 --- a/packages/patterns/test/test-patterns.js +++ b/packages/patterns/test/test-patterns.js @@ -532,6 +532,7 @@ const runTests = (t, successCase, failCase) => { t.throws( () => { copyMapComparison || Fail`No CopyMap comparison support`; + // @ts-expect-error FIXME Key types successCase(specimen, M.gt(makeCopyMap([]))); }, { message: 'No CopyMap comparison support' }, From 418b2dad5976824e0d5639860f2670a826b90a73 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 9 Jan 2024 09:56:13 -0800 Subject: [PATCH 5/6] chore(types): misc accommodations tighten Pattern better RemotableObject WIP punt getInterfaceOf WIP fix sorting restore PassableCap fix valMap type runtime fix decodeErrorCommon acknowledge deferred work fixes to get types building --- packages/captp/src/captp.js | 4 ++- packages/exo/tsconfig.json | 2 +- packages/far/test/test-marshal-far-obj.js | 2 ++ packages/marshal/src/deeplyFulfilled.js | 2 +- packages/marshal/src/encodeToCapData.js | 10 +++---- packages/marshal/src/encodeToSmallcaps.js | 4 +-- packages/marshal/src/marshal.js | 24 +++++++++------ packages/marshal/src/rankOrder.js | 20 +++++++++++-- packages/marshal/src/types.js | 4 +-- packages/marshal/test/test-marshal-capdata.js | 2 ++ packages/marshal/test/test-marshal-far-obj.js | 1 + .../marshal/test/test-marshal-smallcaps.js | 1 + .../marshal/test/test-marshal-stringify.js | 2 ++ packages/patterns/src/keys/checkKey.js | 29 ++++++++++--------- packages/patterns/src/keys/compareKeys.js | 13 ++++++++- packages/patterns/src/keys/copyBag.js | 11 +++++-- packages/patterns/src/keys/copySet.js | 10 +++++-- .../src/keys/keycollection-operators.js | 3 ++ .../patterns/src/keys/merge-bag-operators.js | 5 ++-- .../patterns/src/keys/merge-set-operators.js | 4 +-- packages/patterns/src/types.js | 8 +++-- 21 files changed, 111 insertions(+), 50 deletions(-) diff --git a/packages/captp/src/captp.js b/packages/captp/src/captp.js index 3171aa72ce..7d1fc29520 100644 --- a/packages/captp/src/captp.js +++ b/packages/captp/src/captp.js @@ -326,8 +326,9 @@ export const makeCapTP = ( const IS_REMOTE_PUMPKIN = harden({}); /** - * @type {import('@endo/marshal').ConvertSlotToVal} + * @type {import('@endo/marshal').ConvertValToSlot} */ + // @ts-expect-error intentional hack const assertValIsLocal = val => { const slot = valToSlot.get(val); if (slot && slot[1] === '-') { @@ -499,6 +500,7 @@ export const makeCapTP = ( } // If we imported this slot, mark it as one our peer exported. + // @ts-expect-error map lacks value type return slotToImported.get(recvSlot.add(slot)); } diff --git a/packages/exo/tsconfig.json b/packages/exo/tsconfig.json index 7927a61da1..182dd6244c 100644 --- a/packages/exo/tsconfig.json +++ b/packages/exo/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.eslint-base.json", "compilerOptions": { - "maxNodeModuleJsDepth": 1 + "maxNodeModuleJsDepth": 2 }, "include": [ "*.js", diff --git a/packages/far/test/test-marshal-far-obj.js b/packages/far/test/test-marshal-far-obj.js index f4ecd7091f..600b086660 100644 --- a/packages/far/test/test-marshal-far-obj.js +++ b/packages/far/test/test-marshal-far-obj.js @@ -66,6 +66,7 @@ const testRecord = ({ }), ); +/** @type {import('@endo/pass-style').PassStyled<'remotable'>} */ const goodRemotableProto = testRecord(); // @ts-ignore We're testing bad things anyway @@ -129,6 +130,7 @@ test('passStyleOf validation of remotables', t => { t.throws(() => passStyleOf(badRemotableProto3), NON_METHOD); t.throws(() => passStyleOf(badRemotableProto4), NON_METHOD); + // @ts-expect-error UNTIL https://github.com/microsoft/TypeScript/issues/38385 t.is(passStyleOf(sub(goodRemotableProto)), 'remotable'); t.throws(() => passStyleOf(sub(badRemotableProto1)), EXPECTED_PASS_STYLE); diff --git a/packages/marshal/src/deeplyFulfilled.js b/packages/marshal/src/deeplyFulfilled.js index eda563da37..15873be355 100644 --- a/packages/marshal/src/deeplyFulfilled.js +++ b/packages/marshal/src/deeplyFulfilled.js @@ -53,7 +53,6 @@ export const deeplyFulfilled = async val => { case 'copyRecord': { // @ts-expect-error FIXME narrowed const names = ownKeys(val); - // @ts-expect-error FIXME narrowed const valPs = names.map(name => deeplyFulfilled(val[name])); return E.when(Promise.all(valPs), vals => harden(fromEntries(vals.map((c, i) => [names[i], c]))), @@ -65,6 +64,7 @@ export const deeplyFulfilled = async val => { return E.when(Promise.all(valPs), vals => harden(vals)); } case 'tagged': { + // @ts-expect-error FIXME narrowed const tag = getTag(val); // @ts-expect-error FIXME narrowed return E.when(deeplyFulfilled(val.payload), payload => diff --git a/packages/marshal/src/encodeToCapData.js b/packages/marshal/src/encodeToCapData.js index 5546072a71..a8303a5667 100644 --- a/packages/marshal/src/encodeToCapData.js +++ b/packages/marshal/src/encodeToCapData.js @@ -20,7 +20,7 @@ import { /** @typedef {import('@endo/pass-style').Passable} Passable */ /** @typedef {import('./types.js').Encoding} Encoding */ -/** @typedef {import('@endo/pass-style').Remotable} Remotable */ +/** @typedef {import('@endo/pass-style').RemotableObject} RemotableObject */ /** @typedef {import('./types.js').EncodingUnion} EncodingUnion */ const { ownKeys } = Reflect; @@ -62,7 +62,7 @@ const qclassMatches = (encoded, qclass) => /** * @typedef {object} EncodeToCapDataOptions * @property {( - * remotable: Remotable, + * remotable: RemotableObject, * encodeRecur: (p: Passable) => Encoding * ) => Encoding} [encodeRemotableToCapData] * @property {( @@ -117,7 +117,7 @@ export const makeEncodeToCapData = (encodeOptions = {}) => { * Readers must not care about this order anyway. We impose this requirement * mainly to reduce non-determinism exposed outside a vat. * - * @param {Passable} passable + * @param {any} passable * @returns {Encoding} except that `encodeToCapData` does not generally * `harden` this result before returning. Rather, `encodeToCapData` is not * directly exposed. @@ -269,11 +269,11 @@ harden(makeEncodeToCapData); * @property {( * encodedRemotable: Encoding, * decodeRecur: (e: Encoding) => Passable - * ) => (Promise|Remotable)} [decodeRemotableFromCapData] + * ) => (Promise|RemotableObject)} [decodeRemotableFromCapData] * @property {( * encodedPromise: Encoding, * decodeRecur: (e: Encoding) => Passable - * ) => (Promise|Remotable)} [decodePromiseFromCapData] + * ) => (Promise|RemotableObject)} [decodePromiseFromCapData] * @property {( * encodedError: Encoding, * decodeRecur: (e: Encoding) => Passable diff --git a/packages/marshal/src/encodeToSmallcaps.js b/packages/marshal/src/encodeToSmallcaps.js index f96c51b841..ae6f88c61a 100644 --- a/packages/marshal/src/encodeToSmallcaps.js +++ b/packages/marshal/src/encodeToSmallcaps.js @@ -18,7 +18,7 @@ import { } from '@endo/pass-style'; /** @typedef {import('@endo/pass-style').Passable} Passable */ -/** @typedef {import('@endo/pass-style').Remotable} Remotable */ +/** @typedef {import('@endo/pass-style').RemotableObject} Remotable */ // @typedef {import('./types.js').SmallcapsEncoding} SmallcapsEncoding */ // @typedef {import('./types.js').SmallcapsEncodingUnion} SmallcapsEncodingUnion */ /** @typedef {any} SmallcapsEncoding */ @@ -161,7 +161,7 @@ export const makeEncodeToSmallcaps = (encodeOptions = {}) => { * Readers must not care about this order anyway. We impose this requirement * mainly to reduce non-determinism exposed outside a vat. * - * @param {Passable} passable + * @param {any} passable * @returns {SmallcapsEncoding} except that `encodeToSmallcaps` does not generally * `harden` this result before returning. Rather, `encodeToSmallcaps` is not * directly exposed. diff --git a/packages/marshal/src/marshal.js b/packages/marshal/src/marshal.js index 181e28887d..4500a5b9e1 100644 --- a/packages/marshal/src/marshal.js +++ b/packages/marshal/src/marshal.js @@ -79,7 +79,7 @@ export const makeMarshal = ( const slotMap = new Map(); /** - * @param {Remotable | Promise} passable + * @param {import('@endo/pass-style').PassableCap} passable * @returns {{index: number, repeat: boolean}} */ const encodeSlotCommon = passable => { @@ -134,7 +134,7 @@ export const makeMarshal = ( if (serializeBodyFormat === 'capdata') { /** - * @param {Passable} passable + * @param {import('@endo/pass-style').PassableCap} passable * @param {InterfaceSpec} [iface] * @returns {Encoding} */ @@ -148,9 +148,11 @@ export const makeMarshal = ( } }; + /** @type {(promise: import('@endo/pass-style').RemotableObject, encodeRecur: (p: Passable) => Encoding) => Encoding} */ const encodeRemotableToCapData = (val, _encodeRecur) => encodeSlotToCapData(val, getInterfaceOf(val)); + /** @type {(promise: Promise, encodeRecur: (p: Passable) => Encoding) => Encoding} */ const encodePromiseToCapData = (promise, _encodeRecur) => encodeSlotToCapData(promise); @@ -184,7 +186,7 @@ export const makeMarshal = ( } else if (serializeBodyFormat === 'smallcaps') { /** * @param {string} prefix - * @param {Passable} passable + * @param {import('@endo/pass-style').PassableCap} passable * @param {InterfaceSpec} [iface] * @returns {string} */ @@ -231,7 +233,7 @@ export const makeMarshal = ( }; const makeFullRevive = slots => { - /** @type {Map} */ + /** @type {Map} */ const valMap = new Map(); /** @@ -242,8 +244,9 @@ export const makeMarshal = ( const { iface = undefined, index, ...rest } = slotData; ownKeys(rest).length === 0 || Fail`unexpected encoded slot properties ${q(ownKeys(rest))}`; - if (valMap.has(index)) { - return valMap.get(index); + const extant = valMap.get(index); + if (extant) { + return extant; } // TODO SECURITY HAZARD: must enfoce that remotable vs promise // is according to the encoded string. @@ -267,11 +270,12 @@ export const makeMarshal = ( // are for reuse by other encodings that do, such as smallcaps. const dName = decodeRecur(name); const dMessage = decodeRecur(message); - const dErrorId = errorId && decodeRecur(errorId); + const dErrorId = /** @type {string} */ (errorId && decodeRecur(errorId)); typeof dName === 'string' || Fail`invalid error name typeof ${q(typeof dName)}`; - typeof dMessage === 'string' || - Fail`invalid error message typeof ${q(typeof dMessage)}`; + if (typeof dMessage !== 'string') { + throw Fail`invalid error message typeof ${q(typeof dMessage)}`; + } const EC = getErrorConstructor(dName) || Error; // errorId is a late addition so be tolerant of its absence. const errorName = @@ -330,7 +334,9 @@ export const makeMarshal = ( }; const reviveFromSmallcaps = makeDecodeFromSmallcaps({ + // @ts-expect-error FIXME decodeRemotableFromSmallcaps, + // @ts-expect-error FIXME decodePromiseFromSmallcaps, decodeErrorFromSmallcaps, }); diff --git a/packages/marshal/src/rankOrder.js b/packages/marshal/src/rankOrder.js index cdeea95af4..f8638c9b2c 100644 --- a/packages/marshal/src/rankOrder.js +++ b/packages/marshal/src/rankOrder.js @@ -144,16 +144,20 @@ export const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => { case 'string': { // Within each of these passStyles, the rank ordering agrees with // JavaScript's relational operators `<` and `>`. + // @ts-expect-error FIXME narrowed if (left < right) { return -1; } else { + // @ts-expect-error FIXME narrowed assert(left > right); return 1; } } case 'symbol': { return comparator( + // @ts-expect-error FIXME narrowed nameForPassableSymbol(left), + // @ts-expect-error FIXME narrowed nameForPassableSymbol(right), ); } @@ -167,9 +171,11 @@ export const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => { } // The rank ordering of non-NaN numbers agrees with JavaScript's // relational operators '<' and '>'. + // @ts-expect-error FIXME narrowed if (left < right) { return -1; } else { + // @ts-expect-error FIXME narrowed assert(left > right); return 1; } @@ -186,7 +192,9 @@ export const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => { // of these names, which we then compare lexicographically. This ensures // that if the names of record X are a subset of the names of record Y, // then record X will have an earlier rank and sort to the left of Y. + // @ts-expect-error FIXME narrowed const leftNames = recordNames(left); + // @ts-expect-error FIXME narrowed const rightNames = recordNames(right); const result = comparator(leftNames, rightNames); @@ -194,14 +202,18 @@ export const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => { return result; } return comparator( + // @ts-expect-error FIXME narrowed recordValues(left, leftNames), + // @ts-expect-error FIXME narrowed recordValues(right, rightNames), ); } case 'copyArray': { // Lexicographic + // @ts-expect-error FIXME narrowed const len = Math.min(left.length, right.length); for (let i = 0; i < len; i += 1) { + // @ts-expect-error FIXME narrowed const result = comparator(left[i], right[i]); if (result !== 0) { return result; @@ -209,14 +221,17 @@ export const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => { } // If all matching elements were tied, then according to their lengths. // If array X is a prefix of array Y, then X has an earlier rank than Y. + // @ts-expect-error FIXME narrowed return comparator(left.length, right.length); } case 'tagged': { // Lexicographic by `[Symbol.toStringTag]` then `.payload`. + // @ts-expect-error FIXME narrowed const labelComp = comparator(getTag(left), getTag(right)); if (labelComp !== 0) { return labelComp; } + // @ts-expect-error FIXME narrowed return comparator(left.payload, right.payload); } default: { @@ -286,9 +301,10 @@ harden(assertRankSorted); * function. This is a genuine bug for us NOW because sometimes we sort * in reverse order by passing a reversed rank comparison function. * - * @param {Iterable} passables + * @template {Passable} T + * @param {Iterable} passables * @param {RankCompare} compare - * @returns {Passable[]} + * @returns {T[]} */ export const sortByRank = (passables, compare) => { if (Array.isArray(passables)) { diff --git a/packages/marshal/src/types.js b/packages/marshal/src/types.js index 47ea1fc8be..5ac06ce43e 100644 --- a/packages/marshal/src/types.js +++ b/packages/marshal/src/types.js @@ -172,8 +172,8 @@ export {}; * ordering would also compare magnitudes, and so agree with the rank ordering * of all values other than `NaN`. An array sorted by rank would enable range * queries by magnitude. - * @param {any} left a Passable - * @param {any} right a Passable + * @param {import("@endo/pass-style").Passable} left + * @param {import("@endo/pass-style").Passable} right * @returns {RankComparison} */ diff --git a/packages/marshal/test/test-marshal-capdata.js b/packages/marshal/test/test-marshal-capdata.js index a828ac7e2f..740a997562 100644 --- a/packages/marshal/test/test-marshal-capdata.js +++ b/packages/marshal/test/test-marshal-capdata.js @@ -311,6 +311,7 @@ test('records', t => { const fauxPresence = harden({}); const { serialize: ser, unserialize: unser } = makeMarshal( _val => 'slot', + // @ts-expect-error mock _slot => fauxPresence, { errorTagging: 'off', @@ -404,6 +405,7 @@ test('capdata proto problems', t => { test('capdata slot leniency', t => { const { unserialize: fromCapData } = makeMarshal( undefined, + // @ts-expect-error mock _slot => ({ name: 'I should not be in a slot', }), diff --git a/packages/marshal/test/test-marshal-far-obj.js b/packages/marshal/test/test-marshal-far-obj.js index a194160730..4fd45c078a 100644 --- a/packages/marshal/test/test-marshal-far-obj.js +++ b/packages/marshal/test/test-marshal-far-obj.js @@ -149,6 +149,7 @@ test('passStyleOf validation of remotables', t => { t.throws(() => passStyleOf(badRemotableProto3), NON_METHOD); t.throws(() => passStyleOf(badRemotableProto4), NON_METHOD); + // @ts-expect-error UNTIL https://github.com/microsoft/TypeScript/issues/38385 t.is(passStyleOf(sub(goodRemotableProto)), 'remotable'); t.throws(() => passStyleOf(sub(badRemotableProto1)), EXPECTED_PASS_STYLE); diff --git a/packages/marshal/test/test-marshal-smallcaps.js b/packages/marshal/test/test-marshal-smallcaps.js index d413e87b68..ebc164a00b 100644 --- a/packages/marshal/test/test-marshal-smallcaps.js +++ b/packages/marshal/test/test-marshal-smallcaps.js @@ -165,6 +165,7 @@ test('smallcaps records', t => { const fauxPresence = harden({}); const { serialize: ser, unserialize: unser } = makeMarshal( _val => 'slot', + // @ts-expect-error mock _slot => fauxPresence, { errorTagging: 'off', diff --git a/packages/marshal/test/test-marshal-stringify.js b/packages/marshal/test/test-marshal-stringify.js index 4b33c9b302..3a0d2cdd3f 100644 --- a/packages/marshal/test/test-marshal-stringify.js +++ b/packages/marshal/test/test-marshal-stringify.js @@ -39,9 +39,11 @@ test('marshal stringify errors', t => { t.throws(() => stringify({}), { message: /Cannot pass non-frozen objects like .*. Use harden()/, }); + // @ts-expect-error intentional error t.throws(() => stringify(harden(new Uint8Array(1))), { message: 'Cannot pass mutable typed arrays like "[Uint8Array]".', }); + // @ts-expect-error intentional error t.throws(() => stringify(harden(new Int16Array(1))), { message: 'Cannot pass mutable typed arrays like "[Int16Array]".', }); diff --git a/packages/patterns/src/keys/checkKey.js b/packages/patterns/src/keys/checkKey.js index 148537f6f7..4cdd2334c1 100644 --- a/packages/patterns/src/keys/checkKey.js +++ b/packages/patterns/src/keys/checkKey.js @@ -100,11 +100,13 @@ harden(assertScalarKey); // ////////////////////////////// Keys ///////////////////////////////////////// +// @ts-expect-error Key does not satisfy WeakKey /** @type {WeakSet} */ +// @ts-expect-error Key does not satisfy WeakKey const keyMemo = new WeakSet(); /** - * @param {Passable} val + * @param {Key} val * @param {Checker} check * @returns {boolean} */ @@ -132,7 +134,7 @@ export const checkKey = (val, check) => { harden(checkKey); /** - * @param {Passable} val + * @param {Key} val * @returns {boolean} */ export const isKey = val => checkKey(val, identChecker); @@ -140,6 +142,7 @@ harden(isKey); /** * @param {Key} val + * @returns {asserts val is Key} */ export const assertKey = val => { checkKey(val, assertChecker); @@ -155,7 +158,7 @@ harden(assertKey); const copySetMemo = new WeakSet(); /** - * @param {Passable} s + * @param {any} s * @param {Checker} check * @returns {boolean} */ @@ -198,7 +201,7 @@ export const assertCopySet = s => { harden(assertCopySet); /** - * @template K + * @template {Key} K * @param {CopySet} s * @returns {K[]} */ @@ -209,7 +212,7 @@ export const getCopySetKeys = s => { harden(getCopySetKeys); /** - * @template K + * @template {Key} K * @param {CopySet} s * @param {(key: K, index: number) => boolean} fn * @returns {boolean} @@ -219,7 +222,7 @@ export const everyCopySetKey = (s, fn) => harden(everyCopySetKey); /** - * @template K + * @template {Key} K * @param {Iterable} elementIter * @returns {CopySet} */ @@ -239,7 +242,7 @@ harden(makeCopySet); const copyBagMemo = new WeakSet(); /** - * @param {Passable} b + * @param {any} b * @param {Checker} check * @returns {boolean} */ @@ -282,7 +285,7 @@ export const assertCopyBag = b => { harden(assertCopyBag); /** - * @template K + * @template {Key} K * @param {CopyBag} b * @returns {CopyBag['payload']} */ @@ -293,7 +296,7 @@ export const getCopyBagEntries = b => { harden(getCopyBagEntries); /** - * @template K + * @template {Key} K * @param {CopyBag} b * @param {(entry: [K, bigint], index: number) => boolean} fn * @returns {boolean} @@ -303,7 +306,7 @@ export const everyCopyBagEntry = (b, fn) => harden(everyCopyBagEntry); /** - * @template K + * @template {Key} K * @param {Iterable<[K,bigint]>} bagEntryIter * @returns {CopyBag} */ @@ -315,7 +318,7 @@ export const makeCopyBag = bagEntryIter => { harden(makeCopyBag); /** - * @template K + * @template {Key} K * @param {Iterable} elementIter * @returns {CopyBag} */ @@ -348,7 +351,7 @@ harden(makeCopyBagFromElements); const copyMapMemo = new WeakSet(); /** - * @param {Passable} m + * @param {any} m * @param {Checker} check * @returns {boolean} */ @@ -543,7 +546,7 @@ harden(makeCopyMap); // //////////////////////// Keys Recur ///////////////////////////////////////// /** - * @param {Passable} val + * @param {any} val * @param {Checker} check * @returns {boolean} */ diff --git a/packages/patterns/src/keys/compareKeys.js b/packages/patterns/src/keys/compareKeys.js index 7beee3a65a..b86325897d 100644 --- a/packages/patterns/src/keys/compareKeys.js +++ b/packages/patterns/src/keys/compareKeys.js @@ -28,7 +28,7 @@ const { quote: q, Fail } = assert; * X is equivalent to Y iff the condition 1 holds but condition 2 does not. */ export const setCompare = makeCompareCollection( - /** @type {(s: CopySet) => Array<[K, 1]>} */ ( + /** @type {(s: CopySet) => Array<[K, 1]>} */ ( s => harden(getCopySetKeys(s).map(key => [key, 1])) ), 0, @@ -134,8 +134,10 @@ export const compareKeys = (left, right) => { // rank order. // Because the invariants above apply to the elements of the array, // they apply to the array as a whole. + // @ts-expect-error FIXME narrowed const len = Math.min(left.length, right.length); for (let i = 0; i < len; i += 1) { + // @ts-expect-error FIXME narrowed const result = compareKeys(left[i], right[i]); if (result !== 0) { return result; @@ -143,11 +145,14 @@ export const compareKeys = (left, right) => { } // If all matching elements are keyEQ, then according to their lengths. // Thus, if array X is a prefix of array Y, then X is smaller than Y. + // @ts-expect-error FIXME narrowed return compareRank(left.length, right.length); } case 'copyRecord': { // Pareto partial order comparison. + // @ts-expect-error FIXME narrowed const leftNames = recordNames(left); + // @ts-expect-error FIXME narrowed const rightNames = recordNames(right); // eslint-disable-next-line no-use-before-define @@ -159,7 +164,9 @@ export const compareKeys = (left, right) => { // to avoid more irrelevant ones. return NaN; } + // @ts-expect-error FIXME narrowed const leftValues = recordValues(left, leftNames); + // @ts-expect-error FIXME narrowed const rightValues = recordValues(right, rightNames); // Presume that both copyRecords have the same key order // until encountering a property disproving that hypothesis. @@ -190,7 +197,9 @@ export const compareKeys = (left, right) => { return result; } case 'tagged': { + // @ts-expect-error FIXME narrowed const leftTag = getTag(left); + // @ts-expect-error FIXME narrowed const rightTag = getTag(right); if (leftTag !== rightTag) { // different tags are incommensurate @@ -198,9 +207,11 @@ export const compareKeys = (left, right) => { } switch (leftTag) { case 'copySet': { + // @ts-expect-error FIXME narrowed return setCompare(left, right); } case 'copyBag': { + // @ts-expect-error FIXME narrowed return bagCompare(left, right); } case 'copyMap': { diff --git a/packages/patterns/src/keys/copyBag.js b/packages/patterns/src/keys/copyBag.js index 71c897ac21..9c09e7eef4 100644 --- a/packages/patterns/src/keys/copyBag.js +++ b/packages/patterns/src/keys/copyBag.js @@ -19,7 +19,7 @@ const { details: X } = assert; /** @typedef {import('@endo/pass-style').Passable} Passable */ /** - * @template T + * @template {Key} T * @param {[T,bigint][]} bagEntries * @param {FullCompare | undefined} fullCompare If provided and `bagEntries` is already * known to be sorted by this `fullCompare`, then we should get a memo hit @@ -55,7 +55,7 @@ const checkNoDuplicateKeys = (bagEntries, fullCompare, check) => { }; /** - * @template T + * @template {Key} T * @param {[T,bigint][]} bagEntries * @param {FullCompare} [fullCompare] * @returns {void} @@ -100,6 +100,7 @@ export const checkBagEntries = (bagEntries, check) => { ); } } + // @ts-expect-error FIXME Key types return checkNoDuplicateKeys(bagEntries, undefined, check); }; harden(checkBagEntries); @@ -114,6 +115,10 @@ export const assertBagEntries = bagEntries => { }; harden(assertBagEntries); +/** + * @template {import('../types.js').Key} K + * @param {Iterable<[K, bigint]>} bagEntriesList + */ export const coerceToBagEntries = bagEntriesList => { const bagEntries = sortByRank(bagEntriesList, compareAntiRank); assertBagEntries(bagEntries); @@ -122,7 +127,7 @@ export const coerceToBagEntries = bagEntriesList => { harden(coerceToBagEntries); /** - * @template K + * @template {import('../types.js').Key} K * @param {Iterable<[K, bigint]>} bagEntryIter * @returns {CopyBag} */ diff --git a/packages/patterns/src/keys/copySet.js b/packages/patterns/src/keys/copySet.js index e72dd65516..04f86a7577 100644 --- a/packages/patterns/src/keys/copySet.js +++ b/packages/patterns/src/keys/copySet.js @@ -19,7 +19,7 @@ const { details: X } = assert; /** @typedef {import('@endo/pass-style').Passable} Passable */ /** - * @template T + * @template {Passable} T * @param {T[]} elements * @param {FullCompare | undefined} fullCompare If provided and `elements` is already known * to be sorted by this `fullCompare`, then we should get a memo hit rather @@ -52,7 +52,7 @@ const checkNoDuplicates = (elements, fullCompare, check) => { }; /** - * @template T + * @template {Passable} T * @param {T[]} elements * @param {FullCompare} [fullCompare] * @returns {void} @@ -88,6 +88,10 @@ export const assertElements = elements => { }; harden(assertElements); +/** + * @template {import('../types.js').Key} K + * @param {Iterable} elementsList + */ export const coerceToElements = elementsList => { const elements = sortByRank(elementsList, compareAntiRank); assertElements(elements); @@ -96,7 +100,7 @@ export const coerceToElements = elementsList => { harden(coerceToElements); /** - * @template K + * @template {import('../types.js').Key} K * @param {Iterable} elementIter * @returns {CopySet} */ diff --git a/packages/patterns/src/keys/keycollection-operators.js b/packages/patterns/src/keys/keycollection-operators.js index 8e2b46f465..75440a6761 100644 --- a/packages/patterns/src/keys/keycollection-operators.js +++ b/packages/patterns/src/keys/keycollection-operators.js @@ -27,6 +27,7 @@ const { quote: q, Fail } = assert; * @returns {IterableIterator<[import('../types.js').Key, V]>} */ const generateFullSortedEntries = (entries, rankCompare, fullCompare) => { + // @ts-expect-error FIXME Key types assertRankSorted(entries, rankCompare); const { length } = entries; let i = 0; @@ -56,8 +57,10 @@ const generateFullSortedEntries = (entries, rankCompare, fullCompare) => { // Sort the ties by `fullCompare`, enforce key uniqueness, and delegate to // a sub-iterator. + // @ts-expect-error FIXME Key types const sortedTies = sortByRank(ties, fullCompare); for (let k = 1; k < sortedTies.length; k += 1) { + // @ts-expect-error FIXME Key types const [key0] = sortedTies[k - 1]; const [key1] = sortedTies[k]; Math.sign(fullCompare(key0, key1)) || diff --git a/packages/patterns/src/keys/merge-bag-operators.js b/packages/patterns/src/keys/merge-bag-operators.js index 88a1ce1e50..fdfb4901f5 100644 --- a/packages/patterns/src/keys/merge-bag-operators.js +++ b/packages/patterns/src/keys/merge-bag-operators.js @@ -29,7 +29,7 @@ const { quote: q, Fail } = assert; * to `fullOrder`. However, it optimizes for the case where these contiguous * runs that need to be resorted are either absent or small. * - * @template T + * @template {import('../types').Passable} T * @param {[T,bigint][]} bagEntries * @param {RankCompare} rankCompare * @param {FullCompare} fullCompare @@ -72,6 +72,7 @@ const bagWindowResort = (bagEntries, rankCompare, fullCompare) => { // Providing the same `fullCompare` should cause a memo hit // within `assertNoDuplicates` enabling it to avoid a // redundant resorting. + // @ts-expect-error FIXME Key types assertNoDuplicateKeys(resorted, fullCompare); // This is the raw JS array iterator whose `.next()` method // does not harden the IteratorResult, in violation of our @@ -96,7 +97,7 @@ const bagWindowResort = (bagEntries, rankCompare, fullCompare) => { * For sets, these counts are always 0 or 1, but this representation * generalizes nicely for bags. * - * @template T + * @template {import('../types').Passable} T * @param {[T,bigint][]} xbagEntries * @param {[T,bigint][]} ybagEntries * @returns {Iterable<[T,bigint,bigint]>} diff --git a/packages/patterns/src/keys/merge-set-operators.js b/packages/patterns/src/keys/merge-set-operators.js index c18e18d070..0daedb2264 100644 --- a/packages/patterns/src/keys/merge-set-operators.js +++ b/packages/patterns/src/keys/merge-set-operators.js @@ -25,7 +25,7 @@ const { quote: q, Fail } = assert; * to `fullOrder`. However, it optimizes for the case where these contiguous * runs that need to be resorted are either absent or small. * - * @template T + * @template {import('../types').Passable} T * @param {T[]} elements * @param {RankCompare} rankCompare * @param {FullCompare} fullCompare @@ -89,7 +89,7 @@ const windowResort = (elements, rankCompare, fullCompare) => { * For sets, these counts are always 0 or 1, but this representation * generalizes nicely for bags. * - * @template T + * @template {import('../types').Passable} T * @param {T[]} xelements * @param {T[]} yelements * @returns {Iterable<[T,bigint,bigint]>} diff --git a/packages/patterns/src/types.js b/packages/patterns/src/types.js index 0318c81c59..63f22e887b 100644 --- a/packages/patterns/src/types.js +++ b/packages/patterns/src/types.js @@ -22,6 +22,7 @@ export {}; /** @typedef {import('@endo/marshal').RankCompare} RankCompare */ /** @typedef {import('@endo/marshal').RankCover} RankCover */ +// FIXME exclude nested Error and Promise /** * @typedef {import('@endo/pass-style').Passable} Key * @@ -61,8 +62,9 @@ export {}; * @returns {string} */ +// FIXME exclude nested Error and Promise /** - * @typedef {Passable} Pattern + * @typedef {Exclude} Pattern * * Patterns are Passable arbitrarily-nested pass-by-copy containers * (CopyArray, CopyRecord, CopySet, CopyBag, CopyMap) in which every @@ -422,14 +424,14 @@ export {}; * The CopyRecord must have all properties that appear on `required`, * but may omit properties that appear on `optional`. * - * @property {(basePatt: CopyRecord<*> | CopyArray<*>, + * @property {(basePatt: CopyRecord | CopyArray, * rest?: Pattern, * ) => Matcher} split * Deprecated. Use `M.splitArray` or `M.splitRecord` instead. * An array or record is split into the first part that is matched by * `basePatt`, and the remainder, which is matched against `rest` if present. * - * @property {(basePatt: CopyRecord<*> | CopyArray<*>, + * @property {(basePatt: CopyRecord | CopyArray, * rest?: Pattern, * ) => Matcher} partial * Deprecated. Use `M.splitArray` or `M.splitRecord` instead. From ddcdad1a3e91c82b8ed56b344c624663b9f54474 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jan 2024 16:52:54 -0800 Subject: [PATCH 6/6] chore(types): consolidate PassStyled type --- packages/pass-style/src/passStyle-helpers.js | 4 ++-- packages/pass-style/src/remotable.js | 2 +- packages/pass-style/src/types.d.ts | 22 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/pass-style/src/passStyle-helpers.js b/packages/pass-style/src/passStyle-helpers.js index 4afb6e0efb..911e3455f9 100644 --- a/packages/pass-style/src/passStyle-helpers.js +++ b/packages/pass-style/src/passStyle-helpers.js @@ -125,7 +125,7 @@ harden(checkNormalProperty); /** * @template {import('./types.js').InterfaceSpec} T - * @param {import('./types.js').TaggedRecord} tagRecord + * @param {import('./types.js').PassStyled} tagRecord * @returns {T} */ export const getTag = tagRecord => tagRecord[Symbol.toStringTag]; @@ -144,7 +144,7 @@ harden(checkPassStyle); const makeCheckTagRecord = checkProto => { /** - * @param {import('./types.js').TaggedRecord} tagRecord + * @param {import('./types.js').PassStyled} tagRecord * @param {PassStyle} passStyle * @param {Checker} [check] * @returns {boolean} diff --git a/packages/pass-style/src/remotable.js b/packages/pass-style/src/remotable.js index 8d4add0968..1f2b91d43a 100644 --- a/packages/pass-style/src/remotable.js +++ b/packages/pass-style/src/remotable.js @@ -169,7 +169,7 @@ const checkRemotable = (val, check) => { /** * Simple semantics, just tell what interface (or undefined) a remotable has. * @type {{ - * (val: import('./types.js').TaggedRecord): T; + * (val: import('./types.js').PassStyled): T; * (val: any): string | undefined; * }} * @returns the interface specification, or undefined diff --git a/packages/pass-style/src/types.d.ts b/packages/pass-style/src/types.d.ts index 040865c05d..65080677cc 100644 --- a/packages/pass-style/src/types.d.ts +++ b/packages/pass-style/src/types.d.ts @@ -33,11 +33,17 @@ export type PassStyle = export type TaggedOrRemotable = 'tagged' | 'remotable'; -export type PassStyled = { +/** + * Tagged has own [PASS_STYLE]: "tagged", [Symbol.toStringTag]: $tag. + * + * Remotable has a prototype chain in which the penultimate object has own [PASS_STYLE]: "remotable", [Symbol.toStringTag]: $iface (where both $tag and $iface must be strings, and the latter must either be "Remotable" or start with "Alleged: " or "DebugName: "). + */ +export type PassStyled = { [PASS_STYLE]: S; + [Symbol.toStringTag]: I; }; -export type ExtractStyle

> = P[typeof PASS_STYLE]; +export type ExtractStyle

> = P[typeof PASS_STYLE]; export type PassByCopy = | Primitive @@ -103,7 +109,7 @@ export type PassStyleOf = { (p: any[]): 'copyArray'; (p: Iterable): 'remotable'; (p: Iterator): 'remotable'; - >(p: T): ExtractStyle; + >(p: T): ExtractStyle; (p: { [key: string]: any }): 'copyRecord'; (p: any): PassStyle; }; @@ -130,12 +136,6 @@ export type PassStyleOf = { * any potential proxies. */ export type PureData = Passable; -export type TaggedRecord< - S extends TaggedOrRemotable, - I extends InterfaceSpec, -> = PassStyled & { - [Symbol.toStringTag]: I; -}; /** * An object marked as remotely accessible using the `Far` or `Remotable` * functions, or a local presence representing such a remote object. @@ -143,7 +143,7 @@ export type TaggedRecord< * A more natural name would be Remotable, but that could be confused with the * value of the `Remotable` export of this module (a function). */ -export type RemotableObject = TaggedRecord< +export type RemotableObject = PassStyled< 'remotable', I >; @@ -171,7 +171,7 @@ export type CopyRecord = Record; export type CopyTagged< Tag extends string = string, Payload extends Passable = any, -> = TaggedRecord<'tagged', Tag> & { +> = PassStyled<'tagged', Tag> & { payload: Payload; }; /**