Skip to content

Commit 80624d4

Browse files
committed
feat: support symbol-named methods in virtual objects
Fixes #5359
1 parent a2cedaf commit 80624d4

File tree

2 files changed

+92
-8
lines changed

2 files changed

+92
-8
lines changed

packages/SwingSet/src/liveslots/virtualObjectManager.js

+12-8
Original file line numberDiff line numberDiff line change
@@ -419,11 +419,9 @@ export function makeVirtualObjectManager(
419419
if (typeof obj !== 'object') {
420420
return 'not';
421421
}
422-
if (Object.getOwnPropertySymbols(obj).length !== 0) {
423-
return 'not';
424-
}
425422
let established;
426-
for (const [_name, value] of Object.entries(obj)) {
423+
for (const prop of Reflect.ownKeys(obj)) {
424+
const value = obj[prop];
427425
let current;
428426
if (typeof value === 'function') {
429427
current = 'one';
@@ -432,6 +430,10 @@ export function makeVirtualObjectManager(
432430
typeof value === 'object' &&
433431
assessFacetiousness(value, true) === 'one'
434432
) {
433+
if (typeof prop === 'symbol') {
434+
// can't have symbol-named facets
435+
return 'not';
436+
}
435437
current = 'many';
436438
} else {
437439
return 'not';
@@ -452,20 +454,22 @@ export function makeVirtualObjectManager(
452454

453455
function copyMethods(behavior) {
454456
const obj = {};
455-
for (const [name, func] of Object.entries(behavior)) {
457+
for (const prop of Reflect.ownKeys(behavior)) {
458+
const func = behavior[prop];
456459
assert.typeof(func, 'function');
457-
obj[name] = func;
460+
obj[prop] = func;
458461
}
459462
return obj;
460463
}
461464

462465
function bindMethods(context, behaviorTemplate) {
463466
const obj = {};
464-
for (const [name, func] of Object.entries(behaviorTemplate)) {
467+
for (const prop of Reflect.ownKeys(behaviorTemplate)) {
468+
const func = behaviorTemplate[prop];
465469
assert.typeof(func, 'function');
466470
const method = (...args) => Reflect.apply(func, null, [context, ...args]);
467471
unweakable.add(method);
468-
obj[name] = method;
472+
obj[prop] = method;
469473
}
470474
return obj;
471475
}

packages/SwingSet/test/virtualObjects/test-virtualObjectManager.js

+80
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,86 @@ test('virtual object operations', t => {
398398
]);
399399
});
400400

401+
test('symbol named methods', t => {
402+
const log = [];
403+
const { defineKind, flushCache, dumpStore } = makeFakeVirtualObjectManager({
404+
cacheSize: 0,
405+
log,
406+
});
407+
408+
const IncSym = Symbol.for('incsym');
409+
410+
const symThingBehavior = {
411+
[IncSym]({ state }) {
412+
state.counter += 1;
413+
return state.counter;
414+
},
415+
get({ state }) {
416+
return state.counter;
417+
},
418+
};
419+
420+
const makeThing = defineKind('symthing', initThing, symThingBehavior);
421+
const tid = 'o+2';
422+
423+
// phase 0: start
424+
t.deepEqual(dumpStore(), [
425+
['kindIDID', '1'],
426+
['vom.vkind.2', '{"kindID":"2","tag":"symthing"}'],
427+
]);
428+
429+
// phase 1: object creations
430+
const thing1 = makeThing('thing-1'); // [t1-0]
431+
// t1-0: 'thing-1' 0 0
432+
const thing2 = makeThing('thing-2', 100); // [t2-0]
433+
// t2-0: 'thing-2' 100 0
434+
t.is(log.shift(), `get kindIDID => undefined`);
435+
t.is(log.shift(), `set kindIDID 1`);
436+
t.is(log.shift(), `set vom.vkind.2 {"kindID":"2","tag":"symthing"}`);
437+
t.is(log.shift(), `set vom.${tid}/1 ${thingVal(0, 'thing-1', 0)}`); // evict t1-0
438+
t.deepEqual(log, []);
439+
t.deepEqual(dumpStore(), [
440+
['kindIDID', '1'],
441+
[`vom.${tid}/1`, thingVal(0, 'thing-1', 0)], // =t1-0
442+
['vom.vkind.2', '{"kindID":"2","tag":"symthing"}'],
443+
]);
444+
445+
// phase 2: call symbol-named method on thing1
446+
t.is(thing1[IncSym](), 1); // [t1-1] evict t2-0
447+
t.is(log.shift(), `set vom.${tid}/2 ${thingVal(100, 'thing-2', 0)}`); // evict t2-0
448+
t.is(log.shift(), `get vom.${tid}/1 => ${thingVal(0, 'thing-1', 0)}`); // load t1-0
449+
t.deepEqual(log, []);
450+
t.deepEqual(dumpStore(), [
451+
['kindIDID', '1'],
452+
[`vom.${tid}/1`, thingVal(0, 'thing-1', 0)], // =t1-0
453+
[`vom.${tid}/2`, thingVal(100, 'thing-2', 0)], // =t2-0
454+
['vom.vkind.2', '{"kindID":"2","tag":"symthing"}'],
455+
]);
456+
457+
// phase 3: call symbol-named method on thing2
458+
t.is(thing2[IncSym](), 101); // [t2-1] evict t1-0
459+
t.is(log.shift(), `set vom.${tid}/1 ${thingVal(1, 'thing-1', 0)}`); // evict t1-1
460+
t.is(log.shift(), `get vom.${tid}/2 => ${thingVal(100, 'thing-2', 0)}`); // load t2-0
461+
t.deepEqual(log, []);
462+
t.deepEqual(dumpStore(), [
463+
['kindIDID', '1'],
464+
[`vom.${tid}/1`, thingVal(1, 'thing-1', 0)], // =t1-1
465+
[`vom.${tid}/2`, thingVal(100, 'thing-2', 0)], // =t2-0
466+
['vom.vkind.2', '{"kindID":"2","tag":"symthing"}'],
467+
]);
468+
469+
// phase 4: flush cache
470+
flushCache(); // [] evict t2-1
471+
t.is(log.shift(), `set vom.${tid}/2 ${thingVal(101, 'thing-2', 0)}`); // evict t2-1
472+
t.deepEqual(log, []);
473+
t.deepEqual(dumpStore(), [
474+
['kindIDID', '1'],
475+
[`vom.${tid}/1`, thingVal(1, 'thing-1', 0)], // =t1-1
476+
[`vom.${tid}/2`, thingVal(101, 'thing-2', 0)], // =t2-1
477+
['vom.vkind.2', '{"kindID":"2","tag":"symthing"}'],
478+
]);
479+
});
480+
401481
test('virtual object cycles using the finish function', t => {
402482
const { vom } = makeFakeVirtualStuff();
403483
const { defineKind } = vom;

0 commit comments

Comments
 (0)