using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Numerics; using System.Runtime.CompilerServices; using Jint.Native.Generator; using Jint.Native.Iterator; using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Interop; namespace Jint.Native; public abstract partial class JsValue : IEquatable { public static readonly JsValue Undefined = new JsUndefined(); public static readonly JsValue Null = new JsNull(); [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal readonly InternalTypes _type; protected JsValue(Types type) { _type = (InternalTypes) type; } internal JsValue(InternalTypes type) { _type = type; } [Pure] internal virtual bool IsArray() => false; [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsIntegerIndexedArray => false; [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsConstructor => false; internal bool IsEmpty => ReferenceEquals(this, JsEmpty.Instance); [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal IteratorInstance GetIterator(Realm realm, GeneratorKind hint = GeneratorKind.Sync, ICallable? method = null) { if (!TryGetIterator(realm, out var iterator, hint, method)) { Throw.TypeError(realm, "The value is not iterable"); return null!; } return iterator; } [Pure] internal IteratorInstance GetIteratorFromMethod(Realm realm, ICallable method) { var iterator = method.Call(this); if (iterator is not ObjectInstance objectInstance) { Throw.TypeError(realm); return null!; } return new IteratorInstance.ObjectIterator(objectInstance); } [Pure] internal virtual bool TryGetIterator( Realm realm, [NotNullWhen(true)] out IteratorInstance? iterator, GeneratorKind hint = GeneratorKind.Sync, ICallable? method = null) { var obj = TypeConverter.ToObject(realm, this); if (method is null) { if (hint == GeneratorKind.Async) { method = obj.GetMethod(GlobalSymbolRegistry.AsyncIterator); if (method is null) { var syncMethod = obj.GetMethod(GlobalSymbolRegistry.Iterator); var syncIteratorRecord = obj.GetIterator(realm, GeneratorKind.Sync, syncMethod); // TODO async CreateAsyncFromSyncIterator(syncIteratorRecord); Throw.NotImplementedException("async"); } } else { method = obj.GetMethod(GlobalSymbolRegistry.Iterator); } } if (method is null) { iterator = null; return false; } var iteratorResult = method.Call(obj, Arguments.Empty) as ObjectInstance; if (iteratorResult is null) { Throw.TypeError(realm, "Result of the Symbol.iterator method is not an object"); } if (iteratorResult is IteratorInstance i) { iterator = i; } else { iterator = new IteratorInstance.ObjectIterator(iteratorResult); } return true; } internal static JsValue ConvertAwaitableToPromise(Engine engine, object obj) { if (obj is Task task) { return ConvertTaskToPromise(engine, task); } #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP if (obj is ValueTask valueTask) { return ConvertTaskToPromise(engine, valueTask.AsTask()); } // ValueTask var asTask = obj.GetType().GetMethod(nameof(ValueTask.AsTask)); if (asTask is not null) { return ConvertTaskToPromise(engine, (Task) asTask.Invoke(obj, parameters: null)!); } #endif return FromObject(engine, JsValue.Undefined); } internal static JsValue ConvertTaskToPromise(Engine engine, Task task) { var (promise, resolve, reject) = engine.RegisterPromise(); task = task.ContinueWith(continuationAction => { if (continuationAction.IsFaulted) { reject(FromObject(engine, continuationAction.Exception)); } else if (continuationAction.IsCanceled) { reject(FromObject(engine, new ExecutionCanceledException())); } else { // Special case: Marshal `async Task` as undefined, as this is `Task` at runtime // See https://github.com/sebastienros/jint/pull/1567#issuecomment-1681987702 if (Task.CompletedTask.Equals(continuationAction)) { resolve(FromObject(engine, JsValue.Undefined)); return; } var result = continuationAction.GetType().GetProperty(nameof(Task.Result)); if (result is not null) { resolve(FromObject(engine, result.GetValue(continuationAction))); } else { resolve(FromObject(engine, JsValue.Undefined)); } } }, // Ensure continuation is completed before unwrapping Promise continuationOptions: TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.ExecuteSynchronously); return promise; } [DebuggerBrowsable(DebuggerBrowsableState.Never)] public Types Type { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _type == InternalTypes.Integer ? Types.Number : (Types) (_type & ~InternalTypes.InternalFlags); } /// /// Creates a valid instance from any instance /// public static JsValue FromObject(Engine engine, object? value) { return FromObjectWithType(engine, value, null); } /// /// Creates a valid instance from any instance, with a type /// public static JsValue FromObjectWithType(Engine engine, object? value, Type? type) { if (value is null) { return Null; } if (value is JsValue jsValue) { return jsValue; } if (engine._objectConverters != null) { foreach (var converter in engine._objectConverters) { if (converter.TryConvert(engine, value, out var result)) { return result; } } } if (DefaultObjectConverter.TryConvert(engine, value, type, out var defaultConversion)) { return defaultConversion; } return null!; } /// /// Converts a to its underlying CLR value. /// /// The underlying CLR value of the instance. public abstract object? ToObject(); /// /// Coerces boolean value from instance. /// internal virtual bool ToBoolean() => _type > InternalTypes.Null; /// /// https://tc39.es/ecma262/#sec-getv /// internal JsValue GetV(Realm realm, JsValue property) { var o = TypeConverter.ToObject(realm, this); return o.Get(property, this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public JsValue Get(JsValue property) { return Get(property, this); } /// /// https://tc39.es/ecma262/#sec-get-o-p /// public virtual JsValue Get(JsValue property, JsValue receiver) { return Undefined; } /// /// https://tc39.es/ecma262/#sec-set-o-p-v-throw /// public virtual bool Set(JsValue property, JsValue value, JsValue receiver) { Throw.NotSupportedException(); return false; } /// /// https://tc39.es/ecma262/#sec-instanceofoperator /// internal bool InstanceofOperator(JsValue target) { if (target is not ObjectInstance oi) { Throw.TypeErrorNoEngine("Right-hand side of 'instanceof' is not an object"); return false; } var instOfHandler = oi.GetMethod(GlobalSymbolRegistry.HasInstance); if (instOfHandler is not null) { return TypeConverter.ToBoolean(instOfHandler.Call(target, this)); } if (!target.IsCallable) { Throw.TypeErrorNoEngine("Right-hand side of 'instanceof' is not callable"); } return target.OrdinaryHasInstance(this); } public override string ToString() { return "None"; } public static bool operator ==(JsValue? a, JsValue? b) { if (a is null) { return b is null; } return b is not null && a.Equals(b); } public static bool operator !=(JsValue? a, JsValue? b) { return !(a == b); } public static implicit operator JsValue(char value) { return JsString.Create(value); } public static implicit operator JsValue(int value) { return JsNumber.Create(value); } public static implicit operator JsValue(uint value) { return JsNumber.Create(value); } public static implicit operator JsValue(double value) { return JsNumber.Create(value); } public static implicit operator JsValue(long value) { return JsNumber.Create(value); } public static implicit operator JsValue(ulong value) { return JsNumber.Create(value); } public static implicit operator JsValue(BigInteger value) { return JsBigInt.Create(value); } public static implicit operator JsValue(bool value) { return value ? JsBoolean.True : JsBoolean.False; } [DebuggerStepThrough] public static implicit operator JsValue(string? value) { return value == null ? Null : JsString.Create(value); } /// /// https://tc39.es/ecma262/#sec-islooselyequal /// protected internal virtual bool IsLooselyEqual(JsValue value) { if (ReferenceEquals(this, value)) { return true; } // TODO move to type specific IsLooselyEqual var x = this; var y = value; if (x.IsNumber() && y.IsString()) { return x.IsLooselyEqual(TypeConverter.ToNumber(y)); } if (x.IsString() && y.IsNumber()) { return y.IsLooselyEqual(TypeConverter.ToNumber(x)); } if (x.IsBoolean()) { return y.IsLooselyEqual(TypeConverter.ToNumber(x)); } if (y.IsBoolean()) { return x.IsLooselyEqual(TypeConverter.ToNumber(y)); } if (y.IsObject() && (x._type & InternalTypes.Primitive) != InternalTypes.Empty) { return x.IsLooselyEqual(TypeConverter.ToPrimitive(y)); } if (x.IsObject() && (y._type & InternalTypes.Primitive) != InternalTypes.Empty) { return y.IsLooselyEqual(TypeConverter.ToPrimitive(x)); } return false; } /// /// Strict equality. /// public override bool Equals(object? obj) => Equals(obj as JsValue); /// /// Strict equality. /// public virtual bool Equals(JsValue? other) => ReferenceEquals(this, other); public override int GetHashCode() => _type.GetHashCode(); /// /// Some values need to be cloned in order to be assigned, like ConcatenatedString. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal JsValue Clone() { // concatenated string and arguments currently may require cloning return (_type & InternalTypes.RequiresCloning) == InternalTypes.Empty ? this : DoClone(); } internal virtual JsValue DoClone() => this; [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsCallable => this is ICallable; /// /// https://tc39.es/ecma262/#sec-ordinaryhasinstance /// internal virtual bool OrdinaryHasInstance(JsValue v) { if (!IsCallable) { return false; } var o = v as ObjectInstance; if (o is null) { return false; } var p = Get(CommonProperties.Prototype); if (p is not ObjectInstance) { Throw.TypeError(o.Engine.Realm, $"Function has non-object prototype '{TypeConverter.ToString(p)}' in instanceof check"); } while (true) { o = o.Prototype; if (o is null) { return false; } if (SameValue(p, o)) { return true; } } } internal static bool SameValue(JsValue x, JsValue y) { if (ReferenceEquals(x, y)) { return true; } var typea = x.Type; var typeb = y.Type; if (typea != typeb) { return false; } switch (typea) { case Types.Number: if (x._type == y._type && x._type == InternalTypes.Integer) { return x.AsInteger() == y.AsInteger(); } var nx = TypeConverter.ToNumber(x); var ny = TypeConverter.ToNumber(y); if (double.IsNaN(nx) && double.IsNaN(ny)) { return true; } if (nx == ny) { if (nx == 0) { // +0 !== -0 return NumberInstance.IsNegativeZero(nx) == NumberInstance.IsNegativeZero(ny); } return true; } return false; case Types.String: return string.Equals(TypeConverter.ToString(x), TypeConverter.ToString(y), StringComparison.Ordinal); case Types.Boolean: return TypeConverter.ToBoolean(x) == TypeConverter.ToBoolean(y); case Types.Undefined: case Types.Null: return true; case Types.Symbol: return x == y; case Types.Object: return x is ObjectWrapper xo && y is ObjectWrapper yo && ReferenceEquals(xo.Target, yo.Target); default: return false; } } internal static IConstructor AssertConstructor(Engine engine, JsValue c) { if (!c.IsConstructor) { Throw.TypeError(engine.Realm, c + " is not a constructor"); } return (IConstructor) c; } }