diff --git a/packages/polyfill/src/emulated-verifier/read-filter.ts b/packages/polyfill/src/emulated-verifier/read-filter.ts index ccff09b..e7c775b 100644 --- a/packages/polyfill/src/emulated-verifier/read-filter.ts +++ b/packages/polyfill/src/emulated-verifier/read-filter.ts @@ -243,6 +243,19 @@ export function makeReadFilteredView(set: S, caps: CapRegistry return self.view; }; + /** + * What a MUTATION may hand back - the same question this file asks of any other member, + * asked of a RESULT rather than of a property. Written once and called from each of + * them, because the defect it answers was three copies of a reasoned-about return value: + * the collection becomes the view, a scalar passes, and anything else refuses rather + * than being guessed at. + */ + const handBack = (out: unknown, target: object, prop: string | symbol): unknown => { + if (out === target) return viewOf(prop); + if (cannotReachAnItem(out)) return out; + throw refusalOf(prop, "the collection view"); + }; + /** What the view answers for one member. The `get` trap and the descriptor trap BOTH * call this, so neither can answer what the other refuses. */ const read = (target: object, prop: string | symbol): unknown => { @@ -334,12 +347,9 @@ export function makeReadFilteredView(set: S, caps: CapRegistry if (prop === "add") { const fn = Reflect.get(target, prop, target); if (typeof fn !== "function") return fn; - return (...args: unknown[]): unknown => { - const out = (fn as (...a: unknown[]) => unknown).apply(target, args); - if (out === target) return viewOf(prop); // chaining lands on the view - if (cannotReachAnItem(out)) return out; - throw refusalOf(prop, "the collection view"); - }; + // `add` chains, so its result is the collection and must become the view. + return (...args: unknown[]): unknown => + handBack((fn as (...a: unknown[]) => unknown).apply(target, args), target, prop); } if (prop === "delete") { const fn = Reflect.get(target, prop, target); @@ -347,7 +357,7 @@ export function makeReadFilteredView(set: S, caps: CapRegistry // The same question `has` already refuses to answer, with a worse consequence. The // two disagreed: `has(item)` said no while `delete(item)` removed it. return (item: unknown): unknown => - keep(item) ? (fn as (i: unknown) => unknown).call(target, item) : false; + keep(item) ? handBack((fn as (i: unknown) => unknown).call(target, item), target, prop) : false; } if (prop === "clear") { const del = Reflect.get(target, "delete", target);