-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Promise for non-Passable is not Passable #2421
base: master
Are you sure you want to change the base?
Changes from all commits
3f7c079
38a1cf6
017b2c8
4bff8f8
db8a4da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,9 @@ import { passStyleOf } from './passStyleOf.js'; | |
import { makeTagged } from './makeTagged.js'; | ||
|
||
/** | ||
* @import {Passable, Primitive, CopyRecord, CopyArray, CopyTagged, RemotableObject} from '@endo/pass-style' | ||
* @import {Passable, CopyRecord, CopyArray, CopyTagged, RemotableObject} from '@endo/pass-style' | ||
*/ | ||
/** @import {Callable, RemotableBrand} from '@endo/eventual-send' */ | ||
|
||
const { ownKeys } = Reflect; | ||
const { fromEntries } = Object; | ||
|
@@ -23,32 +24,16 @@ const { fromEntries } = Object; | |
*/ | ||
|
||
/** | ||
* 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 | ||
* @typedef {boolean extends (T extends never ? true : false) | ||
* ? T // pass through the `any` type | ||
* : T extends PromiseLike<any> | ||
* ? DeeplyAwaited<Awaited<T>> | ||
* : T extends object | ||
* ? T extends (Callable | RemotableBrand<any, any> | RemotableObject) | ||
* ? T | ||
* : Simplify<{[K in keyof T]: DeeplyAwaited<T[K]>}> | ||
* : T} DeeplyAwaited | ||
*/ | ||
|
||
/** | ||
|
@@ -73,7 +58,7 @@ const { fromEntries } = Object; | |
* is for the higher "@endo/patterns" level of abstraction to determine, | ||
* because it defines the `Key` notion in question. | ||
* | ||
* @template {Passable} [T=Passable] | ||
* @template {Passable} T | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to remove the default? In general when I write a type-constrained type parameter, I also provide that type constraint as a default binding of the type parameter. Should I stop doing so? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For generic functions we want to rely on inference. I don't remember exactly why I dropped it, but I think a default doesn't have any advantages here, especially one where the default value is the constraint itself. For type definitions it's a different story as it allows you to reference the bare type without type parameters. |
||
* @param {T} val | ||
* @returns {Promise<DeeplyAwaited<T>>} | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* eslint-disable */ | ||
import { expectType } from 'tsd'; | ||
import { Far } from './make-far'; | ||
import { makeTagged } from './makeTagged'; | ||
import { deeplyFulfilled } from './deeplyFulfilled'; | ||
import type { CopyTagged, Passable, RemotableObject } from './types'; | ||
|
||
const remotable = Far('foo', { | ||
myFunc() { | ||
return 'foo'; | ||
}, | ||
}); | ||
|
||
type MyRemotable = typeof remotable; | ||
type MyNonBrandedRemotable = { myFunc(): 'foo' } & RemotableObject; | ||
|
||
const copyTagged = makeTagged('someTag', remotable); | ||
|
||
const someUnknown: unknown = null; | ||
const someAny: any = null; | ||
|
||
expectType<undefined>(await deeplyFulfilled(undefined)); | ||
expectType<string>(await deeplyFulfilled('str')); | ||
expectType<boolean>(await deeplyFulfilled(true)); | ||
expectType<number>(await deeplyFulfilled(1)); | ||
expectType<bigint>(await deeplyFulfilled(1n)); | ||
expectType<symbol>(await deeplyFulfilled(Symbol.for('foo'))); | ||
expectType<null>(await deeplyFulfilled(null)); | ||
expectType<Error>(await deeplyFulfilled(new Error())); | ||
expectType<MyRemotable>(await deeplyFulfilled(remotable)); | ||
expectType<MyNonBrandedRemotable>( | ||
await deeplyFulfilled(remotable as MyNonBrandedRemotable), | ||
); | ||
expectType<CopyTagged<'someTag', MyRemotable>>( | ||
await deeplyFulfilled(copyTagged), | ||
); | ||
expectType<[]>(await deeplyFulfilled([])); | ||
expectType<{}>(await deeplyFulfilled({})); | ||
|
||
expectType<MyRemotable>(await deeplyFulfilled(Promise.resolve(remotable))); | ||
expectType<{ a: MyRemotable }>( | ||
await deeplyFulfilled(Promise.resolve({ a: Promise.resolve(remotable) })), | ||
); | ||
|
||
// By default TS infer an array type instead of a tuple type | ||
const tuple: [Promise<MyRemotable>] = [Promise.resolve(remotable)]; | ||
|
||
expectType<Passable>(tuple); | ||
expectType<[MyRemotable]>(await deeplyFulfilled(tuple)); | ||
expectType<[MyRemotable]>(await deeplyFulfilled(Promise.resolve(tuple))); | ||
|
||
expectType<CopyTagged<'someOtherTag', MyRemotable>>( | ||
await deeplyFulfilled( | ||
Promise.resolve(makeTagged('someOtherTag', Promise.resolve(remotable))), | ||
), | ||
); | ||
|
||
// @ts-expect-error not passable | ||
deeplyFulfilled(someUnknown); | ||
// @ts-expect-error not passable | ||
deeplyFulfilled(Promise.resolve(someUnknown)); | ||
// @ts-expect-error not passable | ||
deeplyFulfilled(Promise.resolve({ a: Promise.resolve(someUnknown) })); | ||
// @ts-expect-error not passable | ||
deeplyFulfilled(Promise.resolve([Promise.resolve(someUnknown)])); | ||
|
||
expectType<any>(await deeplyFulfilled(someAny)); | ||
expectType<any>(await deeplyFulfilled(Promise.resolve(someAny))); | ||
expectType<{ a: any }>( | ||
await deeplyFulfilled(Promise.resolve({ a: Promise.resolve(someAny) })), | ||
); | ||
expectType<[any]>( | ||
await deeplyFulfilled( | ||
Promise.resolve([Promise.resolve(someAny)] as [Promise<any>]), | ||
), | ||
); |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -54,8 +54,8 @@ export type PassByCopy = | |||||||||
|
||||||||||
export type PassByRef = | ||||||||||
| RemotableObject | ||||||||||
| Promise<RemotableObject> | ||||||||||
| Promise<PassByCopy>; | ||||||||||
| PromiseLike<RemotableObject> | ||||||||||
| PromiseLike<PassByCopy>; | ||||||||||
|
||||||||||
/** | ||||||||||
* A Passable is acyclic data that can be marshalled. It must be hardened to | ||||||||||
|
@@ -80,20 +80,54 @@ export type PassByRef = | |||||||||
* using 'slots'). | ||||||||||
*/ | ||||||||||
export type Passable< | ||||||||||
PC extends PassableCap = PassableCap, | ||||||||||
R extends RemotableObject = RemotableObject, | ||||||||||
E extends Error = Error, | ||||||||||
> = void | Primitive | Container<PC, E> | PC | E; | ||||||||||
|
||||||||||
export type Container<PC extends PassableCap, E extends Error> = | ||||||||||
| CopyArrayI<PC, E> | ||||||||||
| CopyRecordI<PC, E> | ||||||||||
| CopyTaggedI<PC, E>; | ||||||||||
interface CopyArrayI<PC extends PassableCap, E extends Error> | ||||||||||
extends CopyArray<Passable<PC, E>> {} | ||||||||||
interface CopyRecordI<PC extends PassableCap, E extends Error> | ||||||||||
extends CopyRecord<Passable<PC, E>> {} | ||||||||||
interface CopyTaggedI<PC extends PassableCap, E extends Error> | ||||||||||
extends CopyTagged<string, Passable<PC, E>> {} | ||||||||||
AllowPromise extends boolean = any, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does it mean to both type-constrain the type variable There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why you didn't adopt the convention of the previous two type variables and write
Suggested change
? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or, given the other changes
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bit of a hack in the type system. I need to conditionally include a The |
||||||||||
AllowTopLevelPromise extends boolean = AllowPromise, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where do you make use of this fourth parameter? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Internally to avoid including |
||||||||||
> = | ||||||||||
| void | ||||||||||
| Primitive | ||||||||||
| Container<R, E, AllowPromise> | ||||||||||
| R | ||||||||||
| E | ||||||||||
| (true extends AllowTopLevelPromise | ||||||||||
? PromiseLike<Passable<R, E, AllowPromise, false>> | ||||||||||
: never); | ||||||||||
|
||||||||||
export type Container< | ||||||||||
R extends RemotableObject, | ||||||||||
E extends Error, | ||||||||||
AllowPromise extends boolean = any, | ||||||||||
> = | ||||||||||
| CopyArrayI<Passable<R, E, AllowPromise>> | ||||||||||
| CopyRecordI<Passable<R, E, AllowPromise>> | ||||||||||
| CopyTaggedI<Passable<R, E, AllowPromise>>; | ||||||||||
interface CopyArrayI<T extends Passable = any> extends CopyArray<T> {} | ||||||||||
interface CopyRecordI<T extends Passable = any> extends CopyRecord<T> {} | ||||||||||
interface CopyTaggedI<T extends Passable = any> extends CopyTagged<string, T> {} | ||||||||||
|
||||||||||
export type PassableSubset< | ||||||||||
T extends Passable<RemotableObject, Error, false> = Passable< | ||||||||||
RemotableObject, | ||||||||||
Error, | ||||||||||
false | ||||||||||
>, | ||||||||||
AllowPromise extends boolean = false, | ||||||||||
AllowTopLevelPromise extends boolean = AllowPromise, | ||||||||||
> = | ||||||||||
| T | ||||||||||
| SubsetContainer<T, AllowPromise> | ||||||||||
| (true extends AllowTopLevelPromise | ||||||||||
? PromiseLike<PassableSubset<T, AllowPromise, false>> | ||||||||||
: never); | ||||||||||
|
||||||||||
type SubsetContainer< | ||||||||||
T extends Passable<RemotableObject, Error, false>, | ||||||||||
AllowPromise extends boolean = false, | ||||||||||
> = | ||||||||||
| CopyArrayI<PassableSubset<T, AllowPromise>> | ||||||||||
| CopyRecordI<PassableSubset<T, AllowPromise>> | ||||||||||
| CopyTaggedI<PassableSubset<T, AllowPromise>>; | ||||||||||
|
||||||||||
export type PassStyleOf = { | ||||||||||
(p: undefined): 'undefined'; | ||||||||||
|
@@ -103,15 +137,15 @@ export type PassStyleOf = { | |||||||||
(p: bigint): 'bigint'; | ||||||||||
(p: symbol): 'symbol'; | ||||||||||
(p: null): 'null'; | ||||||||||
(p: Promise<any>): 'promise'; | ||||||||||
(p: PromiseLike<any>): 'promise'; | ||||||||||
(p: Error): 'error'; | ||||||||||
(p: CopyTagged): 'tagged'; | ||||||||||
(p: any[]): 'copyArray'; | ||||||||||
(p: Iterable<any>): 'remotable'; | ||||||||||
(p: Iterator<any, any, undefined>): 'remotable'; | ||||||||||
<T extends PassStyled<TaggedOrRemotable, any>>(p: T): ExtractStyle<T>; | ||||||||||
(p: { [key: string]: any }): 'copyRecord'; | ||||||||||
(p: any): PassStyle; | ||||||||||
(p: any): never; | ||||||||||
}; | ||||||||||
/** | ||||||||||
* A Passable is PureData when its entire data structure is free of PassableCaps | ||||||||||
|
@@ -135,7 +169,7 @@ export type PassStyleOf = { | |||||||||
* trip (as exists between vats) to produce data structures disconnected from | ||||||||||
* any potential proxies. | ||||||||||
*/ | ||||||||||
export type PureData = Passable<never, never>; | ||||||||||
export type PureData = Passable<never, never, false>; | ||||||||||
/** | ||||||||||
* An object marked as remotely accessible using the `Far` or `Remotable` | ||||||||||
* functions, or a local presence representing such a remote object. | ||||||||||
|
@@ -150,7 +184,11 @@ export type RemotableObject<I extends InterfaceSpec = string> = PassStyled< | |||||||||
/** | ||||||||||
* The authority-bearing leaves of a Passable's pass-by-copy superstructure. | ||||||||||
*/ | ||||||||||
export type PassableCap = Promise<any> | RemotableObject; | ||||||||||
export type PassableCap< | ||||||||||
R extends RemotableObject = RemotableObject, | ||||||||||
AllowPromise extends boolean = any, | ||||||||||
> = R | (true extends AllowPromise ? PromiseLike<R> : never); | ||||||||||
|
||||||||||
/** | ||||||||||
* A Passable sequence of Passable values. | ||||||||||
*/ | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. I'lll just mention that I'm a bit surprised that
RemotableBrand
is defined by @endo/eventual-send butRemotableObject
is defined by @endo/pass-style. No change suggested.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah they're very different, somewhat orthogonal things. I've been discussing with @michaelfig, and from eventual-send's pov, the target or arguments don't require any specific pass-style.
RemotableBrand
helps the typing to capture the shape of remote methods (and the shape of local data) without exposing any actual property on the type itself (its storing them in the private fields of a "stamped" class).RemotableObject
is the pass-style brand, which includes the 2 symbol properties (PASS_STYLE
andSymbol.toStringTag
), denoting a remotable in passables.