#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue using Jint.Native.Array; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors.Specialized; using Jint.Runtime.Interop; namespace Jint.Native.Function; /// /// https://tc39.es/ecma262/#sec-properties-of-the-function-prototype-object /// internal sealed class FunctionPrototype : Function { internal FunctionPrototype( Engine engine, Realm realm, ObjectPrototype objectPrototype) : base(engine, realm, JsString.Empty) { _prototype = objectPrototype; _length = new PropertyDescriptor(JsNumber.PositiveZero, PropertyFlag.Configurable); } protected override void Initialize() { const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; const PropertyFlag LengthFlags = PropertyFlag.Configurable; var properties = new PropertyDictionary(7, checkExistingKeys: false) { ["constructor"] = new LazyPropertyDescriptor(this, static prototype => prototype._realm.Intrinsics.Function, PropertyFlag.NonEnumerable), ["toString"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toString", prototype.ToString, 0, LengthFlags), PropertyFlags), ["apply"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "apply", prototype.Apply, 2, LengthFlags), PropertyFlags), ["call"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "call", prototype.CallImpl, 1, LengthFlags), PropertyFlags), ["bind"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "bind", prototype.Bind, 1, LengthFlags), PropertyFlags), ["arguments"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable), ["caller"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable) }; SetProperties(properties); var symbols = new SymbolDictionary(1) { [GlobalSymbolRegistry.HasInstance] = new PropertyDescriptor(new ClrFunction(_engine, "[Symbol.hasInstance]", HasInstance, 1, PropertyFlag.Configurable), PropertyFlag.AllForbidden) }; SetSymbols(symbols); } /// /// https://tc39.es/ecma262/#sec-function.prototype-@@hasinstance /// private static JsValue HasInstance(JsValue thisObject, JsCallArguments arguments) { return thisObject.OrdinaryHasInstance(arguments.At(0)); } /// /// https://tc39.es/ecma262/#sec-function.prototype.bind /// private JsValue Bind(JsValue thisObject, JsCallArguments arguments) { if (thisObject is not (ICallable and ObjectInstance oi)) { Throw.TypeError(_realm, "Bind must be called on a function"); return default; } var thisArg = arguments.At(0); var f = BoundFunctionCreate(oi, thisArg, arguments.Skip(1)); JsNumber l; var targetHasLength = oi.HasOwnProperty(CommonProperties.Length); if (targetHasLength) { var targetLen = oi.Get(CommonProperties.Length); if (targetLen is not JsNumber number) { l = JsNumber.PositiveZero; } else { if (number.IsPositiveInfinity()) { l = number; } else if (number.IsNegativeInfinity()) { l = JsNumber.PositiveZero; } else { var targetLenAsInt = (long) TypeConverter.ToIntegerOrInfinity(targetLen); // first argument is target var argumentsLength = System.Math.Max(0, arguments.Length - 1); l = JsNumber.Create((ulong) System.Math.Max(targetLenAsInt - argumentsLength, 0)); } } } else { l = JsNumber.PositiveZero; } f.DefinePropertyOrThrow(CommonProperties.Length, new PropertyDescriptor(l, PropertyFlag.Configurable)); var targetName = oi.Get(CommonProperties.Name); if (!targetName.IsString()) { targetName = JsString.Empty; } f.SetFunctionName(targetName, "bound"); return f; } /// /// https://tc39.es/ecma262/#sec-boundfunctioncreate /// private BindFunction BoundFunctionCreate(ObjectInstance targetFunction, JsValue boundThis, JsValue[] boundArgs) { var proto = targetFunction.GetPrototypeOf(); var obj = new BindFunction(_engine, _realm, proto, targetFunction, boundThis, boundArgs); return obj; } /// /// https://tc39.es/ecma262/#sec-function.prototype.tostring /// private JsValue ToString(JsValue thisObject, JsCallArguments arguments) { if (thisObject.IsObject() && thisObject.IsCallable) { return thisObject.ToString(); } Throw.TypeError(_realm, "Function.prototype.toString requires that 'this' be a Function"); return null; } /// /// https://tc39.es/ecma262/#sec-function.prototype.apply /// private JsValue Apply(JsValue thisObject, JsCallArguments arguments) { var func = thisObject as ICallable; if (func is null) { Throw.TypeError(_realm); } var thisArg = arguments.At(0); var argArray = arguments.At(1); if (argArray.IsNullOrUndefined()) { return func.Call(thisArg, Arguments.Empty); } var argList = CreateListFromArrayLike(_realm, argArray); var result = func.Call(thisArg, argList); return result; } /// /// https://tc39.es/ecma262/#sec-createlistfromarraylike /// internal static JsValue[] CreateListFromArrayLike(Realm realm, JsValue argArray, Types? elementTypes = null) { var argArrayObj = argArray as ObjectInstance; if (argArrayObj is null) { Throw.TypeError(realm); } var operations = ArrayOperations.For(argArrayObj, forWrite: false); var argList = elementTypes is null ? operations.GetAll() : operations.GetAll(elementTypes.Value); return argList; } /// /// https://tc39.es/ecma262/#sec-function.prototype.call /// private JsValue CallImpl(JsValue thisObject, JsCallArguments arguments) { var func = thisObject as ICallable; if (func is null) { Throw.TypeError(_realm); } JsValue[] values = []; if (arguments.Length > 1) { values = new JsValue[arguments.Length - 1]; System.Array.Copy(arguments, 1, values, 0, arguments.Length - 1); } var result = func.Call(arguments.At(0), values); return result; } protected internal override JsValue Call(JsValue thisObject, JsCallArguments arguments) { return Undefined; } }