// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
-import { ITERATOR, toFunctionName, toLength, type } from "./value.js";
+import {
+ ITERATOR,
+ toFunctionName,
+ toLength,
+ type,
+ UNDEFINED,
+} from "./value.js";
+import {
+ defineOwnDataProperty,
+ defineOwnProperties,
+ defineOwnProperty,
+ getOwnPropertyDescriptor,
+ getPrototype,
+ objectCreate,
+ setPropertyValues,
+ setPrototype,
+} from "./object.js";
export const {
/**
} = functionPrototype;
const objectConstructor = Object;
const proxyConstructor = Proxy;
- const {
- create: objectCreate,
- defineProperty: defineOwnProperty,
- defineProperties: defineOwnProperties,
- getOwnPropertyDescriptor,
- getPrototypeOf: getPrototype,
- setPrototypeOf: setPrototype,
- } = Object;
const {
apply: reflectApply,
construct: reflectConstruct,
};
},
};
+ 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) {
+ if (base === UNDEFINED) {
// No base function was provided to apply.
return $;
} else {
}
};
const applyProperties = ($, override) => {
- if (override === undefined) {
+ if (override === UNDEFINED) {
// No properties were provided to apply.
return $;
} else {
// Properties were provided; apply them.
const { length, name, prototype } = override;
if (
- !getOwnPropertyDescriptor($, "prototype")?.writable ||
- prototype === undefined
+ prototype === UNDEFINED ||
+ !getOwnPropertyDescriptor($, "prototype")?.writable
) {
// The provided function has no `.prototype`, its prototype is
// not writable, or no prototype value was provided.
//
// Change the prototype property of the provided function to
// match.
- defineOwnProperty($, "prototype", { value: prototype });
+ defineOwnProperty(
+ $,
+ "prototype",
+ defineOwnDataProperty(
+ objectCreate(null),
+ "value",
+ prototype,
+ ),
+ );
}
return defineOwnProperties($, {
- length: {
- value: toLength(length === undefined ? $.length : length),
- },
- name: {
- value: toFunctionName(
- name === undefined ? $.name ?? "" : name,
- ),
- },
+ length: defineOwnDataProperty(
+ objectCreate(null),
+ "value",
+ toLength(length === UNDEFINED ? $.length : length),
+ ),
+ name: defineOwnDataProperty(
+ objectCreate(null),
+ "value",
+ toFunctionName(name === UNDEFINED ? $.name ?? "" : name),
+ ),
});
}
};
callBind(
$,
boundThis,
- ...objectCreate(
- argumentIterablePrototype,
- { args: { value: boundArgs } },
+ ...defineOwnDataProperty(
+ objectCreate(argumentIterablePrototype),
+ "args",
+ boundArgs,
),
),
- createArrowFunction: ($, propertyOverride = undefined) =>
+ createArrowFunction: ($, propertyOverride = UNDEFINED) =>
applyProperties(
applyBaseFunction(
- (...$s) => reflectApply($, undefined, $s),
+ (...$s) => reflectApply($, UNDEFINED, $s),
$,
),
propertyOverride,
),
- createCallableFunction: ($, propertyOverride = undefined) =>
+ createCallableFunction: ($, propertyOverride = UNDEFINED) =>
applyProperties(
applyBaseFunction(
(...$s) => {
- const iterator = objectCreate(
- argumentIterablePrototype,
- { args: { value: $s } },
+ const iterator = defineOwnDataProperty(
+ objectCreate(argumentIterablePrototype),
+ "args",
+ $s,
)[ITERATOR]();
const { value: thisValue } = iterator.next();
return reflectApply($, thisValue, [...iterator]);
),
propertyOverride,
),
- createIllegalConstructor: ($, propertyOverride = undefined) =>
+ createIllegalConstructor: ($, propertyOverride = UNDEFINED) =>
defineOwnProperty(
applyProperties(
applyBaseFunction(
createProxyConstructor: (
handler,
$,
- newTarget = undefined,
- propertyOverride = undefined,
+ newTarget = UNDEFINED,
+ propertyOverride = UNDEFINED,
) => {
- const constructor = $ === undefined ? objectConstructor : $;
- const target = newTarget === undefined ? constructor : newTarget;
+ const constructor = $ === UNDEFINED
+ ? function ($) {
+ return new objectConstructor($);
+ }
+ : $;
+ const target = newTarget === UNDEFINED ? constructor : newTarget;
const len = toLength(constructor.length);
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}.`,
);
} 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.",
);
} 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.",
);
} else {
- return applyProperties(
+ // The arguments are acceptable.
+ const C = applyProperties(
defineOwnProperties(
setPrototype(
- function C(...$s) {
- if (new.target === undefined) {
+ function (...$s) {
+ if (new.target === UNDEFINED) {
+ // The constructor was not called with new; this is an
+ // error.
throw new TypeError(
`Piscēs: ${
C.name ?? "Proxy"
} must be called with new.`,
);
} else {
+ // The constructor was called with new; return the
+ // appropriate proxy.
const O = reflectConstruct(
constructor,
$s,
target,
);
- return new proxyConstructor(O, handler);
+ const proxy = new proxyConstructor(O, handler);
+ return registerConstructedProxy(C, proxy);
}
},
proxyConstructor,
),
{
- length: { value: len },
- name: {
- value: `${
- toFunctionName(constructor.name ?? "")
- }Proxy`,
- },
- prototype: {
+ length: defineOwnDataProperty(
+ objectCreate(null),
+ "value",
+ len,
+ ),
+ name: defineOwnDataProperty(
+ objectCreate(null),
+ "value",
+ `${toFunctionName(constructor.name ?? "")}Proxy`,
+ ),
+ prototype: setPropertyValues(objectCreate(null), {
configurable: false,
enumerable: false,
- value: undefined,
+ value: UNDEFINED,
writable: false,
- },
- revocable: {
- configurable: true,
- enumerable: false,
- value: defineOwnProperties(
- (...$s) => {
- const O = reflectConstruct(
- constructor,
- $s,
- target,
- );
- return revocable(O, handler);
- },
- {
- length: { value: len },
- name: { 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,
+ }),
+ });
}
},
};
construct(function () {}, [], $)
);
+/**
+ * Calls the provided callback with the provided argument if the
+ * provided argument is not nullish; otherwise, returns the provided
+ * argument unmodified.
+ */
+export const maybe = ($, callback) => $ == null ? $ : callback($);
+
/**
* Returns whether the provided object inherits from the prototype of
* the provided function.