-
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?
Conversation
When I first read this, I thought it was about promises and whether they were passable. Took me a bit to understand that this is actually about the The fact that a passable unresolved promise might later fulfill to a non-passable value is a significant hazard in our system we need to remain aware of, whatever the type system might claim to the contrary. |
This is a good point, maybe this point to a mistake in the approach. The Edit: Ugh I keep confusion what happens between |
I think I found a way to break the recursion issues, albeit with a theoretically breaking change in the type definition of Passable, but in practice I think it's backwards compatible. I also took the opportunity to further narrow the type of I have not tested this change applied to agoric-sdk yet. |
We can use Agoric/agoric-sdk#9385 . I've pointed it to this branch. Results with 017b2c8 |
@@ -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 comment
The 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 comment
The 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.
*/ | ||
/** @import {Callable, RemotableBrand} from '@endo/eventual-send' */ |
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 but RemotableObject
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
and Symbol.toStringTag
), denoting a remotable in passables.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
What does it mean to both type-constrain the type variable AllowPromise
to boolean
but give it a default of any
?
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.
Curious why you didn't adopt the convention of the previous two type variables and write
AllowPromise extends boolean = any, | |
P extends Promise = Promise, |
?
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.
Or, given the other changes
AllowPromise extends boolean = any, | |
P extends PromiseLike = PromiseLike, |
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.
It's a bit of a hack in the type system. I need to conditionally include a PromiseLike
in the union, or a never
. But the PromiseLike
needs to be parametrized with a specific constraint. As such this type parameter cannot be provided as a type (or inferred), but instead is just a flag to say "please allow promises in the union".
The any
default is also a hack to make TS bail out when trying to type match. Without it we wouldn't be able to assign a Passable
without promises (AllowPromise=false
) to ones with it (or really places where we don't care). I don't fully understand why, this is where I ended at experimentally.
interface CopyTaggedI<PC extends PassableCap, E extends Error> | ||
extends CopyTagged<string, Passable<PC, E>> {} | ||
AllowPromise extends boolean = any, | ||
AllowTopLevelPromise extends boolean = AllowPromise, |
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.
Where do you make use of this fourth parameter?
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.
Internally to avoid including PromiseLike
in the union of acceptable types inside the PromiseLike
when recursing the type.
Refs: #2406
Description
@mhofman suggested a fix for the FIXME in #2406. As expected it trip on recursion:
Leaving this PR in draft until that's solved. @mhofman @michaelfig feel free to push commits.
Security Considerations
Scaling Considerations
Documentation Considerations
Testing Considerations
Compatibility Considerations
Upgrade Considerations