Skip to content
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

feat: support M.raw() in method guards #1831

Merged
merged 10 commits into from
Oct 29, 2023
2 changes: 1 addition & 1 deletion packages/exo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export {
makeExo,
} from './src/exo-makers.js';

export { GET_INTERFACE_GUARD } from './src/exo-tools.js';
export { GET_INTERFACE_GUARD } from './src/get-interface.js';
50 changes: 34 additions & 16 deletions packages/exo/src/exo-makers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const DEBUG = getEnvironmentOption('DEBUG', '');
// Turn on to give each exo instance its own toStringTag value.
const LABEL_INSTANCES = DEBUG.split(',').includes('label-instances');

/**
* @template {{}} T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarity/repository consistency nit:

Suggested change
* @template {{}} T
* @template {object} T
(details)
$ patt='[{]([{][}]|object)[}]'
$ git grep -liE "$patt" packages/ | xargs grep --no-filename -oE "$patt" | sort | uniq -c
      9 {{}}
    187 {object}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not the same. Object.create(proto) is a type error unless proto extends {}.

* @param {T} proto
* @param {number} instanceCount
* @returns {T}
*/
const makeSelf = (proto, instanceCount) => {
const self = create(proto);
if (LABEL_INSTANCES) {
Expand Down Expand Up @@ -82,6 +88,21 @@ export const initEmpty = () => emptyRecord;
* @property {ReceiveRevoker} [receiveRevoker]
*/

/**
* @template {Methods} M
* @typedef {M & import('@endo/eventual-send').RemotableBrand<{}, M>} Farable
*/

/**
* @template {Methods} M
* @typedef {Farable<M & import('./get-interface.js').GetInterfaceGuard<M>>} Guarded
*/

/**
* @template {Record<FacetName, Methods>} F
* @typedef {{ [K in keyof F]: Guarded<F[K]> }} GuardedKit
*/

/**
* @template {(...args: any[]) => any} I init function
* @template {Methods} M methods
Expand All @@ -90,9 +111,9 @@ export const initEmpty = () => emptyRecord;
* [K in keyof M]: import("@endo/patterns").MethodGuard
* }> | undefined} interfaceGuard
* @param {I} init
* @param {M & ThisType<{ self: M, state: ReturnType<I> }>} methods
* @param {M & ThisType<{ self: Guarded<M>, state: ReturnType<I> }>} methods
* @param {FarClassOptions<ClassContext<ReturnType<I>, M>>} [options]
* @returns {(...args: Parameters<I>) => (M & import('@endo/eventual-send').RemotableBrand<{}, M>)}
* @returns {(...args: Parameters<I>) => Guarded<M>}
*/
export const defineExoClass = (
tag,
Expand Down Expand Up @@ -120,7 +141,6 @@ export const defineExoClass = (
// Be careful not to freeze the state record
const state = seal(init(...args));
instanceCount += 1;
/** @type {M} */
const self = makeSelf(proto, instanceCount);

// Be careful not to freeze the state record
Expand All @@ -130,9 +150,7 @@ export const defineExoClass = (
if (finish) {
finish(context);
}
return /** @type {M & import('@endo/eventual-send').RemotableBrand<{}, M>} */ (
self
);
return self;
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
};

if (receiveRevoker) {
Expand All @@ -149,13 +167,13 @@ harden(defineExoClass);
* @template {(...args: any[]) => any} I init function
* @template {Record<FacetName, Methods>} F facet methods
* @param {string} tag
* @param {{ [K in keyof F]: import("@endo/patterns").InterfaceGuard<{
* [M in keyof F[K]]: import("@endo/patterns").MethodGuard;
* }> } | undefined} interfaceGuardKit
* @param {{ [K in keyof F]:
* InterfaceGuard<{[M in keyof F[K]]: MethodGuard; }>
* } | undefined} interfaceGuardKit
* @param {I} init
* @param {F & ThisType<{ facets: F, state: ReturnType<I> }> } methodsKit
* @param {FarClassOptions<KitContext<ReturnType<I>,F>>} [options]
* @returns {(...args: Parameters<I>) => F}
* @param {F & { [K in keyof F]: ThisType<{ facets: GuardedKit<F>, state: ReturnType<I> }> }} methodsKit
* @param {FarClassOptions<KitContext<ReturnType<I>, GuardedKit<F>>>} [options]
* @returns {(...args: Parameters<I>) => GuardedKit<F>}
*/
export const defineExoClassKit = (
tag,
Expand Down Expand Up @@ -186,8 +204,8 @@ export const defineExoClassKit = (
// Be careful not to freeze the state record
const state = seal(init(...args));
// Don't freeze context until we add facets
/** @type {KitContext<ReturnType<I>,F>} */
const context = { state, facets: {} };
/** @type {{ state: ReturnType<I>, facets: unknown }} */
const context = { state, facets: null };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is null here correct? The original

Suggested change
const context = { state, facets: null };
const context = { state, facets: {} };

seems more correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intended for the null to make it explicit that facets is not yet initialized. I could have used undefined instead, but I think {} implies that the identity will be kept intact (which it isn't). The code below replaces it with the actual facets object.

instanceCount += 1;
const facets = objectMap(prototypeKit, (proto, facetName) => {
const self = makeSelf(proto, instanceCount);
Expand All @@ -200,7 +218,7 @@ export const defineExoClassKit = (
if (finish) {
finish(context);
}
return context.facets;
return /** @type {GuardedKit<F>} */ (context.facets);
};

if (receiveRevoker) {
Expand All @@ -222,7 +240,7 @@ harden(defineExoClassKit);
* }> | undefined} interfaceGuard CAVEAT: static typing does not yet support `callWhen` transformation
* @param {T} methods
* @param {FarClassOptions<ClassContext<{},T>>} [options]
* @returns {T & import('@endo/eventual-send').RemotableBrand<{}, T>}
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
* @returns {Guarded<T>}
*/
export const makeExo = (tag, interfaceGuard, methods, options = undefined) => {
const makeInstance = defineExoClass(
Expand Down
Loading