From: Lady Date: Sun, 3 Dec 2023 18:24:08 +0000 (-0500) Subject: Add is⸺ method to generated proxy constructors X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/54b756dbfe3e86b528253082215a9354e14313f0 Add is⸺ method to generated proxy constructors --- diff --git a/function.js b/function.js index 5d59af1..2f242fe 100644 --- a/function.js +++ b/function.js @@ -128,6 +128,29 @@ export const { }; }, }; + const { get: wmGet, set: wmSet } = WeakMap.prototype; + const wsConstructor = WeakSet; + const { add: wsAdd, has: wsHas } = WeakSet.prototype; + const proxyConstructorValuesMap = new WeakMap(); + const registerConstructedProxy = (constructor, proxy) => { + const values = (() => { + const existing = reflectApply(wmGet, proxyConstructorValuesMap, [ + constructor, + ]); + if (existing) { + return existing; + } else { + const result = new wsConstructor(); + reflectApply(wmSet, proxyConstructorValuesMap, [ + constructor, + result, + ]); + return result; + } + })(); + reflectApply(wsAdd, values, [proxy]); + return proxy; + }; const applyBaseFunction = ($, base, lengthDelta = 0) => { if (base === UNDEFINED) { // No base function was provided to apply. @@ -251,7 +274,11 @@ export const { newTarget = UNDEFINED, propertyOverride = UNDEFINED, ) => { - const constructor = $ === UNDEFINED ? objectConstructor : $; + const constructor = $ === UNDEFINED + ? function ($) { + return new objectConstructor($); + } + : $; const target = newTarget === UNDEFINED ? constructor : newTarget; const len = toLength(constructor.length); if (!(type(handler) === "object")) { @@ -273,13 +300,13 @@ export const { ); } else { // The arguments are acceptable. - return applyProperties( + const C = applyProperties( defineOwnProperties( setPrototype( - function C(...$s) { + function (...$s) { if (new.target === UNDEFINED) { - // The constructor was not called with new; this is - // an error. + // The constructor was not called with new; this is an + // error. throw new TypeError( `Piscēs: ${ C.name ?? "Proxy" @@ -293,7 +320,8 @@ export const { $s, target, ); - return new proxyConstructor(O, handler); + const proxy = new proxyConstructor(O, handler); + return registerConstructedProxy(C, proxy); } }, proxyConstructor, @@ -315,37 +343,71 @@ export const { value: UNDEFINED, writable: false, }), - revocable: setPropertyValues(objectCreate(null), { - configurable: true, - enumerable: false, - value: defineOwnProperties( - (...$s) => { - const O = reflectConstruct( - constructor, - $s, - target, - ); - return revocable(O, handler); - }, - { - length: defineOwnDataProperty( - objectCreate(null), - "value", - len, - ), - name: defineOwnDataProperty( - objectCreate(null), - "value", - "revocable", - ), - }, - ), - writable: true, - }), }, ), propertyOverride, ); + const { name } = C; + return defineOwnProperties(C, { + revocable: setPropertyValues(objectCreate(null), { + configurable: true, + enumerable: false, + value: defineOwnProperties( + (...$s) => { + const O = reflectConstruct( + constructor, + $s, + target, + ); + const proxy = revocable(O, handler); + return registerConstructedProxy(C, proxy); + }, + { + length: defineOwnDataProperty( + objectCreate(null), + "value", + len, + ), + name: defineOwnDataProperty( + objectCreate(null), + "value", + "revocable", + ), + }, + ), + writable: true, + }), + [`is${name}`]: setPropertyValues(objectCreate(null), { + configurable: true, + enumerable: false, + value: defineOwnProperty( + ($) => { + const values = reflectApply( + wmGet, + proxyConstructorValuesMap, + [C], + ); + if (values === UNDEFINED) { + // No values have been registered for the current + // constructor. + return false; + } else { + // One or more values has been registered for the + // current constructor; return whether the provided + // argument is one. + return reflectApply(wsHas, values, [$]); + } + }, + "name", + defineOwnDataProperty( + objectCreate(null), + "value", + `is${name}`, + ), + ), + writable: true, + }), + }); } }, }; diff --git a/function.test.js b/function.test.js index 09b9f7e..bf0e907 100644 --- a/function.test.js +++ b/function.test.js @@ -10,6 +10,7 @@ import { assert, assertEquals, + assertNotStrictEquals, assertSpyCall, assertSpyCalls, assertStrictEquals, @@ -469,9 +470,6 @@ describe("createIllegalConstructor", () => { Object.getPrototypeOf(constructor.prototype), Object.prototype, ); - console.dir( - Object.getOwnPropertyDescriptors(constructor.prototype), - ); assertEquals( constructor.prototype, Object.create(Object.prototype, { @@ -712,6 +710,95 @@ describe("createProxyConstructor", () => { }); }); + describe("~is[[.name]]", () => { + it("[[GetOwnProperty]] defines the appropriate method", () => { + assertNotStrictEquals( + Object.getOwnPropertyDescriptor( + createProxyConstructor({}), + "isProxy", + ), + undefined, + ); + assertNotStrictEquals( + Object.getOwnPropertyDescriptor( + createProxyConstructor({}, function Base() {}), + "isBaseProxy", + ), + undefined, + ); + assertNotStrictEquals( + Object.getOwnPropertyDescriptor( + createProxyConstructor({}, function Bad() {}, undefined, { + name: "⸺", + }), + "is⸺", + ), + undefined, + ); + }); + + it("[[GetOwnProperty]] has the correct descriptor", () => { + const proxyConstructor = createProxyConstructor({}); + assertEquals( + Object.getOwnPropertyDescriptor( + proxyConstructor, + "isProxy", + ), + { + configurable: true, + enumerable: false, + value: proxyConstructor.isProxy, + writable: true, + }, + ); + }); + + it("[[Call]] returns true for created proxies", () => { + const proxyConstructor = createProxyConstructor({}); + const proxy = new proxyConstructor(); + assertStrictEquals( + proxyConstructor.isProxy(proxy), + true, + ); + }); + + it("[[Call]] returns false for nonproxies", () => { + const constructor = function Base() {}; + const proxyConstructor = createProxyConstructor({}, constructor); + assertStrictEquals( + proxyConstructor.isBaseProxy(new constructor()), + false, + ); + }); + + it("[[Construct]] throws an error", () => { + const proxyConstructor = createProxyConstructor({}); + assertThrows(() => new proxyConstructor.isProxy({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + const proxyConstructor = createProxyConstructor({}); + assertStrictEquals(proxyConstructor.isProxy.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + const proxyConstructor = createProxyConstructor({}); + assertStrictEquals(proxyConstructor.isProxy.name, "isProxy"); + const otherProxyConstructor = createProxyConstructor( + {}, + function Base() {}, + ); + assertStrictEquals( + otherProxyConstructor.isBaseProxy.name, + "isBaseProxy", + ); + }); + }); + }); + describe("~length", () => { it("[[GetOwnProperty]] has the correct descriptor", () => { assertEquals( @@ -739,7 +826,7 @@ describe("createProxyConstructor", () => { { configurable: true, enumerable: false, - value: "ObjectProxy", + value: "Proxy", writable: false, }, );