From: Lady Date: Thu, 24 Jul 2025 03:12:48 +0000 (-0400) Subject: Make minor improvements to function.js X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/e7f1624844b6bb27626d982ac046fddf68845136?hp=0b2e1cbf082ff80ae6fd42d028e08d6382a4622b Make minor improvements to function.js Mostly, this minorly improves the algorithm for overrides when creating various types of function, and includes minor documentation changes. --- diff --git a/function.js b/function.js index 2f242fe..5f9c2b5 100644 --- a/function.js +++ b/function.js @@ -1,11 +1,14 @@ -// ♓🌟 Piscēs ∷ function.js -// ==================================================================== -// -// Copyright © 2022‐2023 Lady [@ Lady’s Computer]. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at . +// SPDX-FileCopyrightText: 2022, 2023, 2025 Lady +// SPDX-License-Identifier: MPL-2.0 +/** + * ⁌ ♓🧩 Piscēs ∷ function.js + * + * Copyright © 2022–2023, 2025 Lady [@ Ladys Computer]. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . + */ import { ITERATOR, @@ -20,78 +23,81 @@ import { defineOwnProperty, getOwnPropertyDescriptor, getPrototype, + hasOwnProperty, objectCreate, setPropertyValues, setPrototype, } from "./object.js"; +const PISCĒS = "♓🧩 Piscēs"; + export const { /** * Creates a bound function from the provided function using the * provided this value and arguments list. * - * ☡ As with `call` and `construct`, the arguments must be passed as + * ☡ As with `call´ and `construct´, the arguments must be passed as * an array. */ bind, /** - * Returns a new function which calls the provided function with its - * first argument as the `this` value and the remaining arguments - * passed through. + * Returns a new arrow function function which wraps the provided + * function and passes its arguments thru. * - * The `length`, `name`, and prototype of the provided function will + * The `length´, `name´, and prototype of the provided function will * be preserved in the new one. A second argument may be used to - * override `length` and `name`. + * override `length´ and `name´. */ createArrowFunction, /** - * Returns a new function which calls the provided function with its - * first argument as the `this` value and the remaining arguments - * passed through. + * Returns a new arrow function which wraps the provided function, + * using its first argument as the this value when calling the + * provided function and passing the remainder thru. * - * The `length`, `name`, and prototype of the provided function will + * The `length´, `name´, and prototype of the provided function will * be preserved in the new one. A second argument may be used to - * override `length` and `name`. + * override `length´ and `name´. * - * ※ This is effectively an alias for `Function::call.bind`. + * ※ This is effectively an alias for `Function::call.bind´. */ createCallableFunction, /** * Returns a constructor which throws whenever it is called but has - * the same `.name` and `.prototype` as the provided value. + * the same `.length´, `.name´, and `.prototype´ as the provided + * value. * - * The `length`, `name`, `prototype`, and prototype of the provided + * The `length´, `name´, `prototype´, and prototype of the provided * function will be preserved in the new one. A second argument may - * be used to override `length`, `name`, and `prototype`. + * be used to override `length´, `name´, and `prototype´. */ createIllegalConstructor, /** - * Returns a constructor which produces a new constructor which wraps + * Returns a constructor which produces a new constructor that wraps * the provided constructor, but returns a proxy of the result using * the provided handler. * * The resulting constructor inherits from, and has the same basic - * shape as, `Proxy`. + * shape as, `Proxy´. * - * If a base constructor is not provided, `Object` will be used. + * If a base constructor is not provided, `Object´ will be used. * * If a third argument is provided, it is used as the target for the * provided constructor when it is constructed. This can be used to * prevent leakage of the provided constructor to superclasses - * through `new.target`. + * through `new.target´. * - * The `length` of the provided function will be preserved in the new - * one. A fourth argument may be used to override `length` and - * `name`. + * The `length´ of the provided function will be preserved in the new + * one. A fourth argument may be used to override `length´ and + * `name´. * - * ※ `.prototype` will be present, but undefined, on the resulting - * constructor. This differs from the behaviour of `Proxy`, for which - * `.prototype` is not present at all. It is not presently possible - * to create a constructor with no `.prototype` property in + * ※ `.prototype´ will be present, but undefined, on the resulting + * constructor. This differs from the behaviour of `Proxy´, for which + * `.prototype´ is not present at all. It is not presently possible + * to create a constructor with no `.prototype´ property in * Ecmascript code. */ createProxyConstructor, @@ -123,7 +129,7 @@ export const { }, next: callBind( arrayIteratorNext, - call(arrayIterator, this.args, []), + reflectApply(arrayIterator, this.args, []), ), }; }, @@ -138,8 +144,13 @@ export const { constructor, ]); if (existing) { + // There is an existing values set for this constructor. + // + // It is returned. return existing; } else { + // There is no existing values set for this constructor so one + // must be created. const result = new wsConstructor(); reflectApply(wmSet, proxyConstructorValuesMap, [ constructor, @@ -156,18 +167,41 @@ export const { // No base function was provided to apply. return $; } else { - // A base function was provided; apply it. - const { length, name, prototype } = base; + // A base function was provided. + // + // Apply it. + const overrides = objectCreate(null); + overrides.length = +base.length + lengthDelta; + overrides.name = base.name; if (getPrototype($) === functionPrototype) { + // The provided function has the function prototype. + // + // Change it to match the base function. setPrototype($, getPrototype(base)); } else { + // The provided function already does not have the function + // prototype, so its prototype is not changed. /* do nothing */ } - return applyProperties($, { - length: +length + lengthDelta, - name, - prototype, - }); + if (hasOwnProperty($, "prototype") && "prototype" in base) { + // The provided function has a `.prototype´ property, and one + // was provided in the base function as well. + try { + // If this does not throw, the provided function is a + // constructor. Its `.prototype´ property is set alongside + // the others. + reflectConstruct(function () {}, [], $); + overrides.prototype = base.prototype; + } catch { + // The provided function is not a constructor. + /* do nothing */ + } + } else { + // The provided function does not have a `.prototype´ property, + // or the base function did not have one. + /* do nothing */ + } + return applyProperties($, overrides); } }; const applyProperties = ($, override) => { @@ -175,21 +209,23 @@ export const { // No properties were provided to apply. return $; } else { - // Properties were provided; apply them. - const { length, name, prototype } = override; + // Properties were provided. + // + // Apply them. + const { length, name } = override; if ( - prototype === UNDEFINED || - !getOwnPropertyDescriptor($, "prototype")?.writable + !("prototype" in override) + || !getOwnPropertyDescriptor($, "prototype")?.writable ) { - // The provided function has no `.prototype`, its prototype is + // The provided function has no `.prototype´, its prototype is // not writable, or no prototype value was provided. // // Do not modify the prototype property of the provided // function. /* do nothing */ } else { - // The provided function is a constructor and a prototype value - // was provided. + // The provided function has a writable `.prototype´ and a + // prototype value was provided. // // Change the prototype property of the provided function to // match. @@ -199,7 +235,7 @@ export const { defineOwnDataProperty( objectCreate(null), "value", - prototype, + override.prototype, ), ); } @@ -284,19 +320,19 @@ export const { if (!(type(handler) === "object")) { // The provided handler is not an object; this is an error. throw new TypeError( - `Piscēs: Proxy handler must be an object, but got: ${handler}.`, + `${PISCĒS}: Proxy handler must be an object, but got: ${handler}.`, ); } else if (!isConstructor(constructor)) { // The provided constructor is not a constructor; this is an // error. throw new TypeError( - "Piscēs: Cannot create proxy constructor from nonconstructible value.", + `${PISCĒS}: Cannot create proxy constructor from nonconstructible value.`, ); } else if (!isConstructor(target)) { // The provided new target is not a constructor; this is an // error. throw new TypeError( - "Piscēs: New target must be a constructor.", + `${PISCĒS}: New target must be a constructor.`, ); } else { // The arguments are acceptable. @@ -305,16 +341,17 @@ export const { setPrototype( 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: ${ + `${PISCĒS}: ${ C.name ?? "Proxy" } must be called with new.`, ); } else { - // The constructor was called with new; return the - // appropriate proxy. + // The constructor was called with new. + // + // Return the appropriate proxy. const O = reflectConstruct( constructor, $s, @@ -393,8 +430,9 @@ export const { return false; } else { // One or more values has been registered for the - // current constructor; return whether the provided - // argument is one. + // current constructor. + // + // Return whether the provided argument is one. return reflectApply(wsHas, values, [$]); } }, @@ -417,7 +455,7 @@ export const { * Calls the provided function with the provided this value and * arguments list. * - * ☡ This is effectively an alias for `Reflect.apply`—the arguments + * ☡ This is effectively an alias for `Reflect.apply´—the arguments * must be passed as an arraylike. */ export const call = createArrowFunction(Reflect.apply, { @@ -425,7 +463,7 @@ export const call = createArrowFunction(Reflect.apply, { }); /** - * Returns whether calling the provided function with no `this` value + * Returns whether calling the provided function with no this value * or arguments completes normally; that is, does not throw an error. * * ☡ This function will throw an error if the provided argument is not @@ -435,16 +473,20 @@ export const completesNormally = ($) => { if (!isCallable($)) { // The provided value is not callable; this is an error. throw new TypeError( - `Piscēs: Cannot determine completion of noncallable value: ${$}`, + `${PISCĒS}: Cannot determine completion of noncallable value: ${$}.`, ); } else { // The provided value is callable. try { - // Attempt to call the function and return true if this succeeds. + // This will throw if calling the function throws. + // + // Otherwise, return true. $(); return true; } catch { - // Calling the function did not succeed; return false. + // Calling the function did not succeed. + // + // Return false. return false; } } @@ -454,7 +496,7 @@ export const completesNormally = ($) => { * Constructs the provided function with the provided arguments list * and new target. * - * ☡ This is effectively an alias for `Reflect.construct`—the + * ☡ This is effectively an alias for `Reflect.construct´—the * arguments must be passed as an arraylike. */ export const construct = createArrowFunction(Reflect.construct); @@ -463,8 +505,8 @@ export const construct = createArrowFunction(Reflect.construct); * Returns the provided value. * * ※ This function can be called as a constructor. When used in an - * `extends` clause and called via `super`, it will set the value of - * `this` to the provided value, enabling it to be extended with + * `extends´ clause and called via `super´, it will set the value of + * `this´ to the provided value, enabling it to be extended with * private class features. */ export const identity = function ($) { @@ -477,9 +519,10 @@ export const isCallable = ($) => typeof $ === "function"; /** Returns whether the provided value is a constructor. */ export const isConstructor = ($) => completesNormally(() => - // Try constructing a new object with the provided value as its - // `new.target`. This will throw if the provided value is not a - // constructor. + // Try constructing a new object using the provided value as + // `new.target´. + // + // ☡ This will throw if the provided value is not a constructor. construct(function () {}, [], $) ); diff --git a/function.test.js b/function.test.js index bf0e907..a5c8311 100644 --- a/function.test.js +++ b/function.test.js @@ -1,11 +1,14 @@ -// ♓🌟 Piscēs ∷ function.test.js -// ==================================================================== -// -// Copyright © 2022–2023 Lady [@ Lady’s Computer]. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at . +// SPDX-FileCopyrightText: 2022, 2023, 2025 Lady +// SPDX-License-Identifier: MPL-2.0 +/** + * ⁌ ♓🧩 Piscēs ∷ function.test.js + * + * Copyright © 2022–2023, 2025 Lady [@ Ladys Computer]. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . + */ import { assert, @@ -338,8 +341,8 @@ describe("createArrowFunction", () => { it("[[Call]] returns a function with no prototype property", () => { assert( - !("prototype" in - createArrowFunction(function () {}, { prototype: {} })), + !("prototype" + in createArrowFunction(function () {}, { prototype: {} })), ); }); @@ -430,8 +433,8 @@ describe("createCallableFunction", () => { it("[[Call]] returns a function with no prototype property", () => { assert( - !("prototype" in - createCallableFunction(function () {}, { prototype: {} })), + !("prototype" + in createCallableFunction(function () {}, { prototype: {} })), ); });