Skip to content

Commit

Permalink
feat(pass-style): Far GET_METHOD_NAMES meta method
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Nov 4, 2023
1 parent 0e42abf commit 6b36af1
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 29 deletions.
6 changes: 6 additions & 0 deletions packages/eventual-send/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
"lint:types": "tsc",
"lint:eslint": "eslint '**/*.js'"
},
"exports": {
"./package.json": "./package.json",
".": "./src/no-shim.js",
"./shim.js": "./shim.js",
"./utils.js": "./utils.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/endojs/endo.git"
Expand Down
1 change: 1 addition & 0 deletions packages/eventual-send/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getMethodNames } from './src/local.js';
29 changes: 16 additions & 13 deletions packages/exo/src/exo-tools.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getMethodNames } from '@endo/eventual-send/src/local.js';
import { getMethodNames } from '@endo/eventual-send/utils.js';
import { E, Far } from '@endo/far';
import {
listDifference,
Expand All @@ -13,6 +13,7 @@ import {
getCopyMapEntries,
} from '@endo/patterns';
import { GET_INTERFACE_GUARD } from './get-interface.js';
import { hasOwnPropertyOf } from '@endo/pass-style';

Check failure on line 16 in packages/exo/src/exo-tools.js

View workflow job for this annotation

GitHub Actions / lint (16.x, ubuntu-latest)

`@endo/pass-style` import should occur before import of `./get-interface.js`

/** @typedef {import('@endo/patterns').Method} Method */
/** @typedef {import('@endo/patterns').MethodGuard} MethodGuard */
Expand Down Expand Up @@ -426,18 +427,20 @@ export const defendPrototype = (
);
}

const getInterfaceGuardMethod = {
[GET_INTERFACE_GUARD]() {
return interfaceGuard;
},
}[GET_INTERFACE_GUARD];
prototype[GET_INTERFACE_GUARD] = bindMethod(
`In ${q(GET_INTERFACE_GUARD)} method of (${tag})`,
contextProvider,
getInterfaceGuardMethod,
thisfulMethods,
undefined,
);
if (interfaceGuard && !hasOwnPropertyOf(prototype, GET_INTERFACE_GUARD)) {
const getInterfaceGuardMethod = {
[GET_INTERFACE_GUARD]() {
return interfaceGuard;
},
}[GET_INTERFACE_GUARD];
prototype[GET_INTERFACE_GUARD] = bindMethod(
`In ${q(GET_INTERFACE_GUARD)} method of (${tag})`,
contextProvider,
getInterfaceGuardMethod,
thisfulMethods,
undefined,
);
}

return Far(
tag,
Expand Down
2 changes: 1 addition & 1 deletion packages/exo/src/get-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export const GET_INTERFACE_GUARD = Symbol.for('getInterfaceGuard');
* [GET_INTERFACE_GUARD]: () =>
* import('@endo/patterns').InterfaceGuard<{
* [K in keyof M]: import('@endo/patterns').MethodGuard
* }> | undefined
* }>
* }} GetInterfaceGuard
*/
7 changes: 4 additions & 3 deletions packages/exo/test/test-exo-wobbly-point.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
/* eslint-disable-next-line import/order */
import { test } from './prepare-test-env-ava.js';

// TODO enable import of getMethodNames without deep import
// eslint-disable-next-line import/order
import { getMethodNames } from '@endo/eventual-send/src/local.js';
import { passStyleOf, Far } from '@endo/pass-style';
import { getMethodNames } from '@endo/eventual-send/utils.js';
import { passStyleOf, Far, GET_METHOD_NAMES } from '@endo/pass-style';
import { M } from '@endo/patterns';
import { defineExoClass } from '../src/exo-makers.js';
import { GET_INTERFACE_GUARD } from '../src/get-interface.js';
Expand Down Expand Up @@ -102,6 +101,7 @@ test('ExoPoint instances', t => {
t.false(pt instanceof ExoPoint);
t.deepEqual(getMethodNames(pt), [
GET_INTERFACE_GUARD,
GET_METHOD_NAMES,
'getX',
'getY',
'setY',
Expand Down Expand Up @@ -154,6 +154,7 @@ test('FarWobblyPoint inheritance', t => {
t.is(passStyleOf(wpt), 'remotable');
t.deepEqual(getMethodNames(wpt), [
GET_INTERFACE_GUARD,
GET_METHOD_NAMES,
'getX',
'getY',
'setY',
Expand Down
4 changes: 3 additions & 1 deletion packages/exo/test/test-heap-classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ test('missing interface', t => {
message:
'In "makeSayHello" method of (greeterMaker): result: "[Symbol(passStyle)]" property expected: "[Function <anon>]"',
});
t.is(greeterMaker[GET_INTERFACE_GUARD](), undefined);
t.throws(() => greeterMaker[GET_INTERFACE_GUARD](), {
message: 'greeterMaker[GET_INTERFACE_GUARD] is not a function',
});
});

const SloppyGreeterI = M.interface('greeter', {}, { sloppy: true });
Expand Down
7 changes: 6 additions & 1 deletion packages/pass-style/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export {
export { passStyleOf, assertPassable } from './src/passStyleOf.js';

export { makeTagged } from './src/makeTagged.js';
export { Remotable, Far, ToFarFunction } from './src/make-far.js';
export {
Remotable,
Far,
ToFarFunction,
GET_METHOD_NAMES,
} from './src/make-far.js';

export {
assertRecord,
Expand Down
1 change: 1 addition & 0 deletions packages/pass-style/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"test": "ava"
},
"dependencies": {
"@endo/eventual-send": "^0.17.6",
"@endo/promise-kit": "^0.2.60",
"@fast-check/ava": "^1.1.5"
},
Expand Down
42 changes: 42 additions & 0 deletions packages/pass-style/src/make-far.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="ses"/>

import { getMethodNames } from '@endo/eventual-send/utils.js';
import { assertChecker, PASS_STYLE } from './passStyle-helpers.js';
import { assertIface, getInterfaceOf, RemotableHelper } from './remotable.js';

Expand Down Expand Up @@ -128,16 +129,57 @@ export const Remotable = (
};
harden(Remotable);

/**
* The name of the automatically added default meta-method for obtaining a
* list of all methods of an object declared with `Far`, or an object that
* inherits from an object declared with `Far`.
*
* Modeled on `GET_INTERFACE_GUARD` from
*
* TODO Name to be bikeshed. Perhaps even whether it is a
* string or symbol to be bikeshed.
*
* TODO Beware that an exo's interface can change across an upgrade,
* so remotes that cache it can become stale.
*/
export const GET_METHOD_NAMES = Symbol.for('getMethodNames');

/**
* Note that the returned method is a thisful method! It must be so that
* it works as expected with far-object inheritance.
*
* @returns {(string|symbol)[]}
*/
const getMethodNamesMethod = harden({
[GET_METHOD_NAMES]() {
getMethodNames(this);
},
})[GET_METHOD_NAMES];

/**
* A concise convenience for the most common `Remotable` use.
*
* For far objects (as opposed to far functions), also adds a miranda
* `GET_METHOD_NAMES` method that returns an array of all the method names,
* if there is not yet any method named `GET_METHOD_NAMES`. (Hence "miranda")
*
* @template {{}} T
* @param {string} farName This name will be prepended with `Alleged: `
* for now to form the `Remotable` `iface` argument.
* @param {T} [remotable] The object used as the remotable
*/
export const Far = (farName, remotable = undefined) => {
const r = remotable === undefined ? /** @type {T} */ ({}) : remotable;
if (typeof r === 'object' && !(GET_METHOD_NAMES in r)) {
// This test excludes far functions, since we currently consider them
// to only have a call-behavior, with no callable methods.
// Beware: Mutates the input argument! But `Remotable`
// * requires the object to be mutable
// * does further mutations,
// * hardens the mutated object before returning it.
// so this mutation is not unprecedented. But it is surprising!
r[GET_METHOD_NAMES] = getMethodNamesMethod;
}
return Remotable(`Alleged: ${farName}`, undefined, r);
};
harden(Far);
Expand Down
23 changes: 16 additions & 7 deletions packages/pass-style/test/test-far-class-instances.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
/* eslint-disable max-classes-per-file */
import { test } from './prepare-test-env-ava.js';

// TODO enable import of getMethodNames without deep import
// eslint-disable-next-line import/order
import { getMethodNames } from '@endo/eventual-send/src/local.js';
import { getMethodNames } from '@endo/eventual-send/utils.js';
import { passStyleOf } from '../src/passStyleOf.js';
import { Far } from '../src/make-far.js';
import { Far, GET_METHOD_NAMES } from '../src/make-far.js';

/**
* Classes whose instances should be Far objects may find it convenient to
Expand Down Expand Up @@ -48,7 +47,7 @@ class FarSubclass2 extends FarSubclass1 {
test('far class instances', t => {
const fb = new FarBaseClass();
t.is(passStyleOf(fb), 'remotable');
t.deepEqual(getMethodNames(fb), ['constructor']);
t.deepEqual(getMethodNames(fb), [GET_METHOD_NAMES, 'constructor']);

t.assert(new fb.constructor() instanceof FarBaseClass);
t.throws(() => fb.constructor(), {
Expand All @@ -60,13 +59,18 @@ test('far class instances', t => {
t.is(passStyleOf(fs1), 'remotable');
t.is(fs1.double(4), 8);
t.assert(new fs1.constructor() instanceof FarSubclass1);
t.deepEqual(getMethodNames(fs1), ['constructor', 'double']);
t.deepEqual(getMethodNames(fs1), [GET_METHOD_NAMES, 'constructor', 'double']);

const fs2 = new FarSubclass2(3);
t.is(passStyleOf(fs2), 'remotable');
t.is(fs2.double(4), 8);
t.is(fs2.doubleAdd(4), 11);
t.deepEqual(getMethodNames(fs2), ['constructor', 'double', 'doubleAdd']);
t.deepEqual(getMethodNames(fs2), [
GET_METHOD_NAMES,
'constructor',
'double',
'doubleAdd',
]);

const yField = new WeakMap();
class FarSubclass3 extends FarSubclass1 {
Expand All @@ -84,7 +88,12 @@ test('far class instances', t => {
t.is(passStyleOf(fs3), 'remotable');
t.is(fs3.double(4), 8);
t.is(fs3.doubleAdd(4), 11);
t.deepEqual(getMethodNames(fs3), ['constructor', 'double', 'doubleAdd']);
t.deepEqual(getMethodNames(fs3), [
GET_METHOD_NAMES,
'constructor',
'double',
'doubleAdd',
]);
});

test('far class instance hardened empty', t => {
Expand Down
7 changes: 4 additions & 3 deletions packages/pass-style/test/test-far-wobbly-point.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
/* eslint-disable max-classes-per-file */
import { test } from './prepare-test-env-ava.js';

// TODO enable import of getMethodNames without deep import
// eslint-disable-next-line import/order
import { getMethodNames } from '@endo/eventual-send/src/local.js';
import { getMethodNames } from '@endo/eventual-send/utils.js';
import { passStyleOf } from '../src/passStyleOf.js';
import { Far } from '../src/make-far.js';
import { Far, GET_METHOD_NAMES } from '../src/make-far.js';

const { apply } = Reflect;

Expand Down Expand Up @@ -66,6 +65,7 @@ test('FarPoint instances', t => {
t.is(passStyleOf(pt), 'remotable');
t.assert(pt instanceof FarPoint);
t.deepEqual(getMethodNames(pt), [
GET_METHOD_NAMES,
'constructor',
'getX',
'getY',
Expand Down Expand Up @@ -104,6 +104,7 @@ test('FarWobblyPoint inheritance', t => {
t.assert(wpt instanceof FarPoint);
t.is(passStyleOf(wpt), 'remotable');
t.deepEqual(getMethodNames(wpt), [
GET_METHOD_NAMES,
'constructor',
'getX',
'getY',
Expand Down

0 comments on commit 6b36af1

Please sign in to comment.