From: Lady Date: Mon, 4 Sep 2023 19:54:46 +0000 (-0400) Subject: Add makeIllegalConstructor to functions.js X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/8e591ec68a18c0c4cc355bcde45747546ed59cd9?ds=inline Add makeIllegalConstructor to functions.js --- diff --git a/function.js b/function.js index d1d337a..5da9f6f 100644 --- a/function.js +++ b/function.js @@ -27,6 +27,16 @@ export const { * ※ This is effectively an alias for `Function::call.bind`. */ makeCallable, + + /** + * Returns a constructor which throws whenever it is called but has + * the same `.name` and `.prototype` as the provided value. + * + * If a second argument is provided, the returned constructor will + * use that as its prototype; otherwise, it will use the prototype of + * the provided value. + */ + makeIllegalConstructor, } = (() => { // ☡ Because these functions are used to initialize module constants, // they can’t depend on imports from elsewhere. @@ -41,6 +51,7 @@ export const { create: objectCreate, defineProperties: defineOwnProperties, getPrototypeOf: getPrototype, + setPrototypeOf: setPrototype, } = Object; const { [ITERATOR]: arrayIterator } = Array.prototype; const { @@ -71,6 +82,31 @@ export const { length: { value: $.length + 1 }, name: { value: name ?? $.name ?? "" }, }), + makeIllegalConstructor: ($, proto = undefined) => { + const constructor = function () { + throw new TypeError("Illegal constructor"); + }; + if ($ == null && proto === undefined) { + // The provided argument is nullish and no explicit prototype + // was provided. + // + // Do not modify the prototype of the generated constructor. + /* do nothing */ + } else { + // The provided argument is not nullish or an explicit + // prototype was provided. + // + // Set the prototype of the generated constructor to match. + setPrototype( + constructor, + proto === undefined ? getPrototype($) : proto, + ); + } + return defineOwnProperties(constructor, { + name: { value: $?.name ?? "" }, + prototype: { value: $?.prototype ?? {}, writable: false }, + }); + }, }; })(); diff --git a/function.test.js b/function.test.js index 35f7f37..2260b5a 100644 --- a/function.test.js +++ b/function.test.js @@ -8,6 +8,7 @@ // file, You can obtain one at . import { + assert, assertEquals, assertStrictEquals, assertThrows, @@ -23,6 +24,7 @@ import { isCallable, isConstructor, makeCallable, + makeIllegalConstructor, ordinaryHasInstance, } from "./function.js"; @@ -427,6 +429,112 @@ describe("makeCallable", () => { }); }); +describe("makeIllegalConstructor", () => { + it("[[Call]] returns a constructor", () => { + assert(isConstructor(makeIllegalConstructor())); + }); + + it("[[Call]] works as expected when provided with no arguments", () => { + const constructor = makeIllegalConstructor(); + assertStrictEquals( + Object.getPrototypeOf(constructor), + Function.prototype, + ); + assertStrictEquals( + Object.getPrototypeOf(constructor.prototype), + Object.prototype, + ); + assertEquals(constructor.prototype, {}); + assert( + !Object.getOwnPropertyDescriptor(constructor, "prototype") + .writable, + ); + assertStrictEquals(constructor.name, ""); + }); + + it("[[Call]] returns a correctly‐formed constructor when provided one argument", () => { + const constructorPrototype = Object.create(null, { + name: { value: "etaoin" }, + prototype: { value: {} }, + }); + const constructor = makeIllegalConstructor( + Object.create(constructorPrototype), + ); + assert(isConstructor(constructor)); + assertStrictEquals( + Object.getPrototypeOf(constructor), + constructorPrototype, + ); + assert(Object.hasOwn(constructor, "prototype")); + assertEquals( + constructor.prototype, + constructorPrototype.prototype, + ); + assert( + !Object.getOwnPropertyDescriptor(constructor, "prototype") + .writable, + ); + assertStrictEquals(constructor.name, "etaoin"); + }); + + it("[[Call]] allows the second argument to override the prototype of the constructor", () => { + const constructorPrototype = Object.create(null, { + name: { value: "etaoin" }, + prototype: { value: {} }, + }); + const expectedPrototype = Object.create(null, { + name: { value: "shrdlu" }, + prototype: { value: {} }, + }); + const constructor = makeIllegalConstructor( + Object.create(constructorPrototype), + expectedPrototype, + ); + assert(isConstructor(constructor)); + assertStrictEquals( + Object.getPrototypeOf(constructor), + expectedPrototype, + ); + assert(Object.hasOwn(constructor, "prototype")); + assertEquals( + constructor.prototype, + constructorPrototype.prototype, + ); + assert( + !Object.getOwnPropertyDescriptor(constructor, "prototype") + .writable, + ); + assertStrictEquals(constructor.name, "etaoin"); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new makeIllegalConstructor(function () {})); + }); + + describe("~", () => { + it("[[Call]] throws an error", () => { + assertThrows(() => { + makeIllegalConstructor(function () {})(); + }); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => { + makeIllegalConstructor(function () {})(); + }); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + makeIllegalConstructor.name, + "makeIllegalConstructor", + ); + }); + }); +}); + describe("ordinaryHasInstance", () => { it("[[Call]] walks the prototype chain", () => { const constructor = class {