Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions packages/ocapn/test/codecs/passable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

/**
* @import { CodecTestEntry } from './_codecs_util.js'
* @import { Passable } from '@endo/pass-style'
*/

import test from '@endo/ses-ava/test.js';
Expand Down Expand Up @@ -189,12 +190,20 @@ const table = [
},
{
name: 'tagged with reference (local promise)',
// We need to cast to Passable because these are Promise<unknown>.
makeValue: testKit =>
makeTagged('promiseTag', testKit.makeLocalPromise(101n)),
makeTagged(
'promiseTag',
/** @type {Extract<Passable, Promise<any>>} */ (
testKit.makeLocalPromise(101n)
),
),
makeExpectedValue: testKit =>
makeTagged(
'promiseTag',
testKit.referenceKit.provideRemotePromiseValue(101n),
/** @type {Extract<Passable, Promise<any>>} */ (
testKit.referenceKit.provideRemotePromiseValue(101n)
),
),
},
{
Expand Down
5 changes: 5 additions & 0 deletions packages/pass-style/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ User-visible changes in `@endo/pass-style`:
that the CopyArray is hardened, so this typing statically reveals where
mutation is attempted. Use `Passable[]` if you want to have a mutable array
that contains only passable values.
- Updates `Passable` to exclude promises that are not assignable to
`Promise<Passable>`. This typing statically reveals when a promise is passed
that will likely fulfil to a non-passable value at runtime, which would
typically be surfaced as a rejection of the original passed promise with a
system error.

# 1.6.3 (2025-07-11)

Expand Down
55 changes: 38 additions & 17 deletions packages/pass-style/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ export type PassByCopy = Atom | Error | CopyArray | CopyRecord | CopyTagged;
export type PassByRef =
| RemotableObject
| RemotableBrand<any, any>
| Promise<RemotableObject>
| Promise<RemotableBrand<any, any>>
| Promise<PassByCopy>;
| Promise<Passable>;

/**
* A Passable is acyclic data that can be marshalled. It must be hardened to
Expand All @@ -108,20 +106,44 @@ export type PassByRef =
* using 'slots').
*/
export type Passable<
PC extends PassableCap = PassableCap,
E extends Error = Error,
> = void | Atom | Container<PC, E> | PC | E;
/**
* Promises are now explicitly part of the Passable types, so the PC
* parameter is only here for backward compatibility for limiting the
* non-promise PassableCaps.
*/
PC extends AwaitedPassableCap = AwaitedPassableCap,
/**
* E was initially just Error (non-keys) or never (keys), now we use it more
* generally to indicate when the Passable is allowed to contain Promises as
* well (never still means keys).
*/
E extends Error | Promise<unknown> = Error | Promise<unknown>,
> =
| AwaitedPassable<PC, E>
| (E extends Promise<unknown> ? Promise<AwaitedPassable<PC, E>> : never);

export type Container<PC extends PassableCap, E extends Error> =
/**
* An AwaitedPassable is the non-Promise parts of Passable.
*/
export type AwaitedPassable<
PC extends AwaitedPassableCap = AwaitedPassableCap,
E extends Error | Promise<any> = Error | Promise<any>,
> = void | Atom | Container<PC, E> | PC | Exclude<E, Promise<any>>;

export type Container<PC extends AwaitedPassableCap, E extends Error | Promise<any>> =
| CopyArrayInterface<PC, E>
| CopyRecordInterface<PC, E>
| CopyTaggedInterface<PC, E>;
export interface CopyArrayInterface<PC extends PassableCap, E extends Error>
export interface CopyArrayInterface<PC extends AwaitedPassableCap, E extends Error | Promise<any>>
extends CopyArray<Passable<PC, E>> {}
export interface CopyRecordInterface<PC extends PassableCap, E extends Error>
extends CopyRecord<Passable<PC, E>> {}
export interface CopyTaggedInterface<PC extends PassableCap, E extends Error>
extends CopyTagged<string, Passable<PC, E>> {}
export interface CopyRecordInterface<
PC extends AwaitedPassableCap,
E extends Error | Promise<any>,
> extends CopyRecord<Passable<PC, E>> {}
export interface CopyTaggedInterface<
PC extends AwaitedPassableCap,
E extends Error | Promise<any>,
> extends CopyTagged<string, Passable<PC, E>> {}

export type PassStyleOf = {
(p: undefined): 'undefined';
Expand All @@ -139,6 +161,7 @@ export type PassStyleOf = {
(p: Iterator<any, any, undefined>): 'remotable';
<T extends PassStyled<PassStyleMarker, any>>(p: T): ExtractStyle<T>;
(p: { [key: string]: any }): 'copyRecord';
(p: unknown): never;
(p: any): PassStyle;
};

Expand Down Expand Up @@ -194,13 +217,11 @@ export type RemotableMethodName = PropertyKey;
/**
* The authority-bearing leaves of a Passable's pass-by-copy superstructure.
*/
export type PassableCap =
| Promise<any>
| RemotableObject
| RemotableBrand<any, any>;
export type PassableCap = AwaitedPassableCap | Promise<Passable>;

/**
* Types you would get from Awaited<PassableCap>
* The non-promise authority-bearing leaves of a Passable's pass-by-copy
* superstructure.
*/
export type AwaitedPassableCap = RemotableObject | RemotableBrand<any, any>;

Expand Down
3 changes: 2 additions & 1 deletion packages/pass-style/src/types.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ expectPassable(['car', 'cdr'] as Readonly<string[]>);
expectPassable(['car', 'cdr'] as Readonly<[string, string]>);
// @ts-expect-error not passable
expectPassable(fn);
// FIXME promise for a non-Passable is not Passable
// @ts-expect-error promise for a non-Passable is not Passable
expectPassable(Promise.resolve(fn));
// @ts-expect-error not passable
expectPassable({ a: { b: fn } });
Expand All @@ -84,6 +84,7 @@ expectPassable({ a: remotable });
expectPassable(copyTagged);
expectPassable(Promise.resolve(remotable));
expectPassable({ a: Promise.resolve(remotable) });
// @ts-expect-error promise for a non-Passable is not Passable
expectPassable({ a: Promise.resolve(fn) });

expectAssignable<Checker>((cond: boolean, details: string) => cond);
9 changes: 3 additions & 6 deletions packages/patterns/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
Passable,
PassStyle,
Atom,
RemotableObject,
AwaitedPassableCap,
} from '@endo/pass-style';
import type {
PartialCompare,
Expand Down Expand Up @@ -72,12 +72,9 @@ export type { FullCompare } from '@endo/marshal';
* in fact only in the mutually incomparable case can the rank be said to
* contain more than one key.
*/
export type Key = Exclude<
Passable<RemotableObject | RemotableBrand<any, any>, never>,
Error | Promise<any>
>;
export type Key = Passable<AwaitedPassableCap, never>;

export type ScalarKey = Atom | RemotableObject | RemotableBrand<any, any>;
export type ScalarKey = Atom | AwaitedPassableCap;

export type KeyToDBKey = (key: Key) => string;
export type GetRankCover = (
Expand Down
Loading