Skip to content

Commit

Permalink
types(marshal): fix #1257 typing of deeplyFulfilled
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Jun 9, 2024
1 parent f845665 commit cfc5f2c
Showing 1 changed file with 71 additions and 20 deletions.
91 changes: 71 additions & 20 deletions packages/marshal/src/deeplyFulfilled.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,52 @@ import { E } from '@endo/eventual-send';
import { isPromise } from '@endo/promise-kit';
import { getTag, isObject, makeTagged, passStyleOf } from '@endo/pass-style';

/** @import {Passable} from '@endo/pass-style' */
/** @import {Passable, Primitive, CopyRecord, CopyArray, CopyTagged, RemotableObject} from '@endo/pass-style' */

import { X, q } from '@endo/errors';

const { ownKeys } = Reflect;
const { fromEntries } = Object;

// TODO return a type contingent on the parameter as deeplyFullfilledObject from agoric-sdk does
/**
* Currently copied from @agoric/internal utils.js.
* TODO Should migrate here and then, if needed, reexported there.
*
* @template T
* @typedef {{ [KeyType in keyof T]: T[KeyType] } & {}} Simplify flatten the
* type output to improve type hints shown in editors
* https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts
*/

/**
* Currently copied from @agoric/internal utils.js.
* TODO Should migrate here and then, if needed, reexported there.
*
* @typedef {(...args: any[]) => any} Callable
*/

/**
* Currently copied from @agoric/internal utils.js.
* TODO Should migrate here and then, if needed, reexported there.
*
* @template {{}} T
* @typedef {{
* [K in keyof T]: T[K] extends Callable ? T[K] : DeeplyAwaited<T[K]>;
* }} DeeplyAwaitedObject
*/

/**
* Currently copied from @agoric/internal utils.js.
* TODO Should migrate here and then, if needed, reexported there.
*
* @template T
* @typedef {T extends PromiseLike<any>
* ? Awaited<T>
* : T extends {}
* ? Simplify<DeeplyAwaitedObject<T>>
* : Awaited<T>} DeeplyAwaited
*/

/**
* Given a Passable `val` whose pass-by-copy structure may contain leaf
* promises, return a promise for a replacement Passable,
Expand All @@ -31,51 +69,64 @@ const { fromEntries } = Object;
*
* If `val` or its parts are non-key Passables only *because* they contains
* promises, the deeply fulfilled forms of val or its parts may be keys. This
* is for the higher "store" level of abstraction to determine, because it
* defines the "key" notion in question.
* is for the higher "@endo/patterns" level of abstraction to determine,
* because it defines the `Key` notion in question.
*
* // TODO: That higher level is in the process of being migrated from
* // `@agoric/store` to `@endo/patterns`. Once that is far enough along,
* // revise the above comment to match.
* // See https://github.com/endojs/endo/pull/1451
*
* @param {any} val
* @returns {Promise<Passable>}
* @template {Passable} [T=Passable]
* @param {T} val
* @returns {Promise<DeeplyAwaited<T>>}
*/
export const deeplyFulfilled = async val => {
// TODO Figure out why we need these at-expect-error directives below
// and fix if possible.
// https://github.com/endojs/endo/issues/1257 may be relevant.

if (!isObject(val)) {
return val;
const prim = /** @type {Primitive} */ (val);
// @ts-expect-error not assignable to type 'DeeplyAwaited<T>'
return prim;
}
if (isPromise(val)) {
return E.when(val, nonp => deeplyFulfilled(nonp));
}
const passStyle = passStyleOf(val);
switch (passStyle) {
case 'copyRecord': {
const names = ownKeys(val);
const valPs = names.map(name => deeplyFulfilled(val[name]));
const rec = /** @type {CopyRecord} */ (val);
const names = /** @type {string[]} */ (ownKeys(rec));
const valPs = names.map(name => deeplyFulfilled(rec[name]));
// @ts-expect-error not assignable to type 'DeeplyAwaited<T>'
return E.when(Promise.all(valPs), vals =>
harden(fromEntries(vals.map((c, i) => [names[i], c]))),
);
}
case 'copyArray': {
const valPs = val.map(p => deeplyFulfilled(p));
const arr = /** @type {CopyArray} */ (val);
const valPs = arr.map(p => deeplyFulfilled(p));
// @ts-expect-error not assignable to type 'DeeplyAwaited<T>'
return E.when(Promise.all(valPs), vals => harden(vals));
}
case 'tagged': {
const tag = getTag(val);
return E.when(deeplyFulfilled(val.payload), payload =>
const tgd = /** @type {CopyTagged} */ (val);
const tag = getTag(tgd);
// @ts-expect-error not assignable to type 'DeeplyAwaited<T>'
return E.when(deeplyFulfilled(tgd.payload), payload =>
makeTagged(tag, payload),
);
}
case 'remotable': {
return val;
const rem = /** @type {RemotableObject} */ (val);
// @ts-expect-error not assignable to type 'DeeplyAwaited<T>'
return rem;
}
case 'error': {
return val;
const err = /** @type {Error} */ (val);
// @ts-expect-error not assignable to type 'DeeplyAwaited<T>'
return err;
}
case 'promise': {
return E.when(val, nonp => deeplyFulfilled(nonp));
const prom = /** @type {Promise} */ (/** @type {unknown} */ (val));
return E.when(prom, nonp => deeplyFulfilled(nonp));
}
default: {
throw assert.fail(X`Unexpected passStyle ${q(passStyle)}`, TypeError);
Expand Down

0 comments on commit cfc5f2c

Please sign in to comment.