Skip to content

Commit

Permalink
fix(vow): correct the typing of unwrap
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Jun 14, 2024
1 parent 76c17c6 commit 40ccba1
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 51 deletions.
9 changes: 8 additions & 1 deletion packages/internal/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable max-classes-per-file */
import type { RemotableBrand } from '@endo/eventual-send';
import type { ERef, RemotableBrand } from '@endo/eventual-send';
import type { Primitive } from '@endo/pass-style';
import type { Callable } from './utils.js';

Expand Down Expand Up @@ -56,3 +56,10 @@ export type DataOnly<T> =
export type Remote<Primary, Local = DataOnly<Primary>> =
| Primary
| RemotableBrand<Local, Primary>;

/**
* Potentially remote promises or settled references.
*/
export type FarRef<Primary, Local = DataOnly<Primary>> = ERef<
Remote<Primary, Local>
>;
82 changes: 44 additions & 38 deletions packages/vow/src/E.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
// @ts-check
/**
* @file provides a `makeE` that can be parameterized with an `unwrap` function
* and corresponding `import('./types').EUnwrap<T>`. These will be used to
* extract the final settlement from a chain of PromiseLikes and PromiseSteps or
* similar non-thenable pseudo-promises.
*
* `@agoric/vow/vat.js` uses this mechanism to export a `V` function with
* similar behaviour as the default `E`, augmented with automatic unwrapping of
* recipient Vows as if they were PromiseLikes.
*/

/*
* TODO: Once this implementation has been polished and well-tested, it is
* designed to be a drop-in replacement for the version in
* `@endo/eventual-send/src/E.js` which contained no concept of "unwrap",
*/
import { trackTurns } from './track-turns.js';
import { makeMessageBreakpointTester } from './message-breakpoints.js';

Expand All @@ -9,9 +25,9 @@ const onSend = makeMessageBreakpointTester('ENDO_SEND_BREAKPOINTS');

/**
* @import { HandledPromiseConstructor, RemotableBrand } from '@endo/eventual-send'
* @import { Unwrap, Vow } from './types.js'
* @import { EUnwrap } from './types.js'
*/
/** @typedef {(...args: any[]) => any} Callable */
/** @typedef {(...args: unknown[]) => any} Callable */

/** @type {ProxyHandler<any>} */
const baseFreezableProxyHandler = {
Expand Down Expand Up @@ -43,7 +59,7 @@ const baseFreezableProxyHandler = {
*
* @param {any} recipient Any value passed to E(x)
* @param {HandledPromiseConstructor} HandledPromise
* @param {(x: any) => Promise<any>} unwrap
* @param {<T>(x: T) => Promise<EUnwrap<T>>} unwrap
* @returns {ProxyHandler<any>} the Proxy handler
*/
const makeEProxyHandler = (recipient, HandledPromise, unwrap) =>
Expand Down Expand Up @@ -108,7 +124,7 @@ const makeEProxyHandler = (recipient, HandledPromise, unwrap) =>
*
* @param {any} recipient Any value passed to E.sendOnly(x)
* @param {HandledPromiseConstructor} HandledPromise
* @param {(x: any) => Promise<any>} unwrap
* @param {<T>(x: T) => Promise<EUnwrap<T>>} unwrap
* @returns {ProxyHandler<any>} the Proxy handler
*/
const makeESendOnlyProxyHandler = (recipient, HandledPromise, unwrap) =>
Expand Down Expand Up @@ -171,7 +187,7 @@ const makeESendOnlyProxyHandler = (recipient, HandledPromise, unwrap) =>
*
* @param {any} x Any value passed to E.get(x)
* @param {HandledPromiseConstructor} HandledPromise
* @param {(x: any) => Promise<any>} unwrap
* @param {<T>(x: T) => Promise<EUnwrap<T>>} unwrap
* @returns {ProxyHandler<any>} the Proxy handler
*/
const makeEGetProxyHandler = (x, HandledPromise, unwrap) =>
Expand All @@ -186,19 +202,17 @@ const resolve = x => HandledPromise.resolve(x);

/**
* @template [A={}]
* @template {(x: any) => Promise<any>} [U=(x: any) => Promise<any>]
* @param {HandledPromiseConstructor} HandledPromise
* @param {object} [powers]
* @param {U} [powers.unwrap]
* @param {<T>(x: T) => Promise<EUnwrap<T>>} [powers.unwrap]
* @param {A} [powers.additional]
*/
const makeE = (
HandledPromise,
{
const makeE = (HandledPromise, powers = {}) => {
const {
additional = /** @type {A} */ ({}),
unwrap = /** @type {U} */ (resolve),
} = {},
) => {
unwrap = /** @type {NonNullable<typeof powers.unwrap>} */ (resolve),
} = powers;

return harden(
assign(
/**
Expand Down Expand Up @@ -264,13 +278,13 @@ const makeE = (

/**
* E.when(x, res, rej) is equivalent to
* unwrapped(x).then(onfulfilled, onrejected)
* unwrap(x).then(onfulfilled, onrejected)
*
* @template T
* @template [TResult1=Unwrap<T>]
* @template [TResult1=EUnwrap<T>]
* @template [TResult2=never]
* @param {ERef<T>} x value to convert to a handled promise
* @param {(value: Unwrap<T>) => ERef<TResult1>} [onfulfilled]
* @param {(value: EUnwrap<T>) => ERef<TResult1>} [onfulfilled]
* @param {(reason: any) => ERef<TResult2>} [onrejected]
* @returns {Promise<TResult1 | TResult2>}
* @readonly
Expand Down Expand Up @@ -310,9 +324,11 @@ export default makeE;

/**
* @template {Callable} T
* @typedef {(
* (...args: Parameters<T>) => Promise<Unwrap<ReturnType<T>>>
* )} ECallable
* @typedef {ReturnType<T> extends EUnwrap<ReturnType<T>>
* ? (...args: Parameters<T>) => Promise<ReturnType<T>>
* : ReturnType<T> extends Promise<EUnwrap<ReturnType<T>>>
* ? T
* : (...args: Parameters<T>) => Promise<EUnwrap<ReturnType<T>>>} ECallable
*/

/**
Expand All @@ -327,7 +343,7 @@ export default makeE;
/**
* @template T
* @typedef {{
* readonly [P in keyof T]: T[P] extends PromiseLike<infer U>
* readonly [P in keyof T]: T[P] extends PromiseLike<any>
* ? T[P]
* : Promise<Awaited<T[P]>>;
* }} EGetters
Expand Down Expand Up @@ -387,7 +403,9 @@ export default makeE;
* @template T
* @typedef {(
* T extends Callable
* ? (...args: Parameters<T>) => ReturnType<T> // a root callable, no methods
* ? (...args: Parameters<T>) => ReturnType<T> // a root callable, no methods
* : FilteredKeys<T, Callable> extends never
* ? never
* : Pick<T, FilteredKeys<T, Callable>> // any callable methods
* )} PickCallable
*/
Expand All @@ -397,30 +415,18 @@ export default makeE;
*
* @template T
* @typedef {(
* T extends RemotableBrand<infer L, infer R> // if a given T is some remote interface R
* ? PickCallable<R> // then return the callable properties of R
* : Awaited<T> extends RemotableBrand<infer L, infer R> // otherwise, if the final resolution of T is some remote interface R
* ? PickCallable<R> // then return the callable properties of R
* : Awaited<T> extends Vow<infer U>
* ? RemoteFunctions<U> // then extract the remotable functions of U
* : T extends PromiseLike<infer U> // otherwise, if T is a promise
* ? Awaited<T> // then return resolved value T
* : T // otherwise, return T
* EUnwrap<T> extends RemotableBrand<any, infer R> // if a given T will settle to some remote interface R
* ? PickCallable<R> // then return the callable properties of R
* : PickCallable<EUnwrap<T>> // otherwise return the callable properties of the settled T
* )} RemoteFunctions
*/

/**
* @template T
* @typedef {(
* T extends RemotableBrand<infer L, infer R>
* T extends RemotableBrand<infer L, any>
* ? L
* : Awaited<T> extends RemotableBrand<infer L, infer R>
* ? L
* : Awaited<T> extends Vow<infer U>
* ? LocalRecord<U>
* : T extends PromiseLike<infer U>
* ? Awaited<T>
* : T
* : EUnwrap<T>
* )} LocalRecord
*/

Expand Down
12 changes: 4 additions & 8 deletions packages/vow/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
export {};

/**
* @import {RemotableBrand} from '@endo/eventual-send'
* @import {CopyTagged} from '@endo/pass-style'
* @import {RemotableObject} from '@endo/pass-style';
* @import {IsPrimitive, Remote} from '@agoric/internal';
* @import {PromiseVow} from '@agoric/vow';
* @import {Remote} from '@agoric/internal';
* @import {prepareVowTools} from './tools.js'
*/

Expand Down Expand Up @@ -36,12 +34,10 @@ export {};
* This is used within E, so we must narrow the type to its remote form.
* @template T
* @typedef {(
* T extends PromiseLike<infer U> ? Unwrap<U> :
* T extends Vow<infer U> ? Unwrap<U> :
* IsPrimitive<T> extends true ? T :
* T extends RemotableBrand<infer Local, infer Primary> ? Local & T :
* T extends Vow<infer U> ? EUnwrap<U> :
* T extends PromiseLike<infer U> ? EUnwrap<U> :
* T
* )} Unwrap
* )} EUnwrap
*/

/**
Expand Down
8 changes: 4 additions & 4 deletions packages/vow/src/when.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-check
import { getVowPayload, basicE } from './vow-utils.js';

/** @import { IsRetryableReason, Unwrap } from './types.js' */
/** @import { IsRetryableReason, EUnwrap } from './types.js' */

/**
* @param {IsRetryableReason} [isRetryableReason]
Expand All @@ -13,10 +13,10 @@ export const makeWhen = (
* Shorten `specimenP` until we achieve a final result.
*
* @template T
* @template [TResult1=Unwrap<T>]
* @template [TResult1=EUnwrap<T>]
* @template [TResult2=never]
* @param {T} specimenP value to unwrap
* @param {(value: Unwrap<T>) => TResult1 | PromiseLike<TResult1>} [onFulfilled]
* @param {(value: EUnwrap<T>) => TResult1 | PromiseLike<TResult1>} [onFulfilled]
* @param {(reason: any) => TResult2 | PromiseLike<TResult2>} [onRejected]
* @returns {Promise<TResult1 | TResult2>}
*/
Expand Down Expand Up @@ -50,7 +50,7 @@ export const makeWhen = (
payload = getVowPayload(result);
}

const unwrapped = /** @type {Unwrap<T>} */ (result);
const unwrapped = /** @type {EUnwrap<T>} */ (result);

// We've extracted the final result.
if (onFulfilled == null && onRejected == null) {
Expand Down

0 comments on commit 40ccba1

Please sign in to comment.