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 6, 2023
1 parent 94389ad commit ca36f77
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 22 deletions.
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';
3 changes: 2 additions & 1 deletion packages/exo/src/exo-tools.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getMethodNames } from '@endo/eventual-send/src/local.js';
import { getMethodNames } from '@endo/eventual-send/utils.js';
import { hasOwnPropertyOf } from '@endo/pass-style';
import { E, Far } from '@endo/far';
import {
listDifference,
Expand Down
4 changes: 3 additions & 1 deletion packages/exo/src/get-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* The name of the automatically added default meta-method for
* obtaining an exo's interface, if it has one.
*
* Intended to be similar to `GET_METHOD_NAMES` from `@endo/pass-style`.
*
* TODO Name to be bikeshed. Perhaps even whether it is a
* string or symbol to be bikeshed.
*
Expand All @@ -18,6 +20,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
12 changes: 8 additions & 4 deletions packages/marshal/test/test-marshal-testing.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { test } from './prepare-test-env-ava.js';

// eslint-disable-next-line import/order
import { Far, passStyleOf, Remotable } from '@endo/pass-style';
import { passStyleOf, Remotable } from '@endo/pass-style';
import { makeMarshal } from '../src/marshal.js';

const { create } = Object;

const alice = Far('alice');
const bob1 = Far('bob');
const bob2 = Far('bob');
// Use the lower level `Remotable` rather than `Far` to make an empty
// far object, i.e., one without even the miranda meta methods like
// `GET_METHOD_NAMES`. Such an empty far object should be `t.deepEqual`
// to its remote presences.
const alice = Remotable('Alleged: alice');
const bob1 = Remotable('Alleged: bob');
const bob2 = Remotable('Alleged: bob');

const convertValToSlot = val =>
passStyleOf(val) === 'remotable' ? 'far' : val;
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
2 changes: 1 addition & 1 deletion packages/pass-style/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
"test": "ava"
},
"dependencies": {
"@endo/eventual-send": "^0.17.6",
"@endo/promise-kit": "^0.2.60",
"@fast-check/ava": "^1.1.5"
},
"devDependencies": {
"@endo/eventual-send": "^0.17.6",
"@endo/init": "^0.5.60",
"@endo/ses-ava": "^0.2.44",
"ava": "^5.3.0",
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 `@endo/exo`.
*
* 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 ca36f77

Please sign in to comment.