diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 7158019a5f..51d414a2af 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -22,8 +22,7 @@ "source-phase-imports", "tail-call-optimization", "Temporal", - "u180e", - "upsert" + "u180e" ], "ExcludedFlags": [ ], diff --git a/Jint/JsValueExtensions.cs b/Jint/JsValueExtensions.cs index 3576e8e8d6..af02a33aaa 100644 --- a/Jint/JsValueExtensions.cs +++ b/Jint/JsValueExtensions.cs @@ -27,7 +27,6 @@ public static bool IsUndefined(this JsValue value) return value._type == InternalTypes.Undefined; } - [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsArray(this JsValue value) @@ -738,4 +737,30 @@ internal static ICallable GetCallable(this JsValue source, Realm realm) return TypeConverter.ToIndex(oi.Engine.Realm, maxByteLength); } + + /// + /// https://tc39.es/ecma262/#sec-canonicalize-keyed-collection-key + /// + internal static JsValue CanonicalizeKeyedCollectionKey(this JsValue key) + { + return key is JsNumber number && number.IsNegativeZero() ? JsNumber.PositiveZero : key; + } + + /// + /// https://tc39.es/ecma262/#sec-canbeheldweakly + /// + internal static bool CanBeHeldWeakly(this JsValue v, Engine engine) + { + if (v.IsObject()) + { + return true; + } + + if (v is JsSymbol symbol && engine.GlobalSymbolRegistry.KeyForSymbol(symbol).IsUndefined()) + { + return true; + } + + return false; + } } diff --git a/Jint/Native/JsMap.cs b/Jint/Native/JsMap.cs index 7050ba5b4e..d377f13f04 100644 --- a/Jint/Native/JsMap.cs +++ b/Jint/Native/JsMap.cs @@ -56,6 +56,30 @@ protected override bool TryGetProperty(JsValue property, [NotNullWhen(true)] out return value; } + internal JsValue GetOrInsert(JsValue key, JsValue value) + { + if (_map.TryGetValue(key, out var temp)) + { + return temp; + } + + _map[key] = value; + return value; + } + + internal JsValue GetOrInsertComputed(JsValue key, ICallable callbackfn) + { + if (_map.TryGetValue(key, out var temp)) + { + return temp; + } + + var value = callbackfn.Call(Undefined, key); + + _map[key] = value; + return value; + } + public new void Set(JsValue key, JsValue value) { if (key is JsNumber number && number.IsNegativeZero()) diff --git a/Jint/Native/JsWeakMap.cs b/Jint/Native/JsWeakMap.cs index c4063f5fd5..a24d5f021d 100644 --- a/Jint/Native/JsWeakMap.cs +++ b/Jint/Native/JsWeakMap.cs @@ -48,4 +48,27 @@ internal JsValue WeakMapGet(JsValue key) return value; } + internal JsValue GetOrInsert(JsValue key, JsValue value) + { + if (_table.TryGetValue(key, out var temp)) + { + return temp; + } + + _table.Add(key, value); + return value; + } + + internal JsValue GetOrInsertComputed(JsValue key, ICallable callbackfn) + { + if (_table.TryGetValue(key, out var temp)) + { + return temp; + } + + var value = callbackfn.Call(Undefined, key); + + _table.Add(key, value); + return value; + } } diff --git a/Jint/Native/Map/MapPrototype.cs b/Jint/Native/Map/MapPrototype.cs index 72fc5a68b4..24fff84837 100644 --- a/Jint/Native/Map/MapPrototype.cs +++ b/Jint/Native/Map/MapPrototype.cs @@ -27,27 +27,29 @@ internal MapPrototype( protected override void Initialize() { - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - var properties = new PropertyDictionary(12, checkExistingKeys: false) + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + var properties = new PropertyDictionary(14, checkExistingKeys: false) { ["length"] = new PropertyDescriptor(0, PropertyFlag.Configurable), ["constructor"] = new PropertyDescriptor(_mapConstructor, PropertyFlag.NonEnumerable), - ["clear"] = new PropertyDescriptor(new ClrFunction(Engine, "clear", Clear, 0, PropertyFlag.Configurable), propertyFlags), - ["delete"] = new PropertyDescriptor(new ClrFunction(Engine, "delete", Delete, 1, PropertyFlag.Configurable), propertyFlags), - ["entries"] = new PropertyDescriptor(new ClrFunction(Engine, "entries", Entries, 0, PropertyFlag.Configurable), propertyFlags), - ["forEach"] = new PropertyDescriptor(new ClrFunction(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), propertyFlags), - ["get"] = new PropertyDescriptor(new ClrFunction(Engine, "get", Get, 1, PropertyFlag.Configurable), propertyFlags), - ["has"] = new PropertyDescriptor(new ClrFunction(Engine, "has", Has, 1, PropertyFlag.Configurable), propertyFlags), - ["keys"] = new PropertyDescriptor(new ClrFunction(Engine, "keys", Keys, 0, PropertyFlag.Configurable), propertyFlags), - ["set"] = new PropertyDescriptor(new ClrFunction(Engine, "set", Set, 2, PropertyFlag.Configurable), propertyFlags), - ["values"] = new PropertyDescriptor(new ClrFunction(Engine, "values", Values, 0, PropertyFlag.Configurable), propertyFlags), + ["clear"] = new PropertyDescriptor(new ClrFunction(Engine, "clear", Clear, 0, PropertyFlag.Configurable), PropertyFlags), + ["delete"] = new PropertyDescriptor(new ClrFunction(Engine, "delete", Delete, 1, PropertyFlag.Configurable), PropertyFlags), + ["entries"] = new PropertyDescriptor(new ClrFunction(Engine, "entries", Entries, 0, PropertyFlag.Configurable), PropertyFlags), + ["forEach"] = new PropertyDescriptor(new ClrFunction(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), PropertyFlags), + ["get"] = new PropertyDescriptor(new ClrFunction(Engine, "get", Get, 1, PropertyFlag.Configurable), PropertyFlags), + ["getOrInsert"] = new PropertyDescriptor(new ClrFunction(Engine, "getOrInsert", GetOrInsert, 2, PropertyFlag.Configurable), PropertyFlags), + ["getOrInsertComputed"] = new PropertyDescriptor(new ClrFunction(Engine, "getOrInsertComputed", GetOrInsertComputed, 2, PropertyFlag.Configurable), PropertyFlags), + ["has"] = new PropertyDescriptor(new ClrFunction(Engine, "has", Has, 1, PropertyFlag.Configurable), PropertyFlags), + ["keys"] = new PropertyDescriptor(new ClrFunction(Engine, "keys", Keys, 0, PropertyFlag.Configurable), PropertyFlags), + ["set"] = new PropertyDescriptor(new ClrFunction(Engine, "set", Set, 2, PropertyFlag.Configurable), PropertyFlags), + ["values"] = new PropertyDescriptor(new ClrFunction(Engine, "values", Values, 0, PropertyFlag.Configurable), PropertyFlags), ["size"] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get size", Size, 0, PropertyFlag.Configurable), set: null, PropertyFlag.Configurable) }; SetProperties(properties); var symbols = new SymbolDictionary(2) { - [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunction(Engine, "iterator", Entries, 1, PropertyFlag.Configurable), propertyFlags), + [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunction(Engine, "iterator", Entries, 1, PropertyFlag.Configurable), PropertyFlags), [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("Map", false, false, true), }; SetSymbols(symbols); @@ -65,6 +67,22 @@ private JsValue Get(JsValue thisObject, JsCallArguments arguments) return map.Get(arguments.At(0)); } + private JsValue GetOrInsert(JsValue thisObject, JsCallArguments arguments) + { + var map = AssertMapInstance(thisObject); + var key = arguments.At(0).CanonicalizeKeyedCollectionKey(); + var value = arguments.At(1); + return map.GetOrInsert(key, value); + } + + private JsValue GetOrInsertComputed(JsValue thisObject, JsCallArguments arguments) + { + var map = AssertMapInstance(thisObject); + var key = arguments.At(0).CanonicalizeKeyedCollectionKey(); + var callbackfn = arguments.At(1).GetCallable(_realm); + return map.GetOrInsertComputed(key, callbackfn); + } + private JsValue Clear(JsValue thisObject, JsCallArguments arguments) { var map = AssertMapInstance(thisObject); diff --git a/Jint/Native/Symbol/GlobalSymbolRegistry.cs b/Jint/Native/Symbol/GlobalSymbolRegistry.cs index 18d96e1231..b3aa4ed66e 100644 --- a/Jint/Native/Symbol/GlobalSymbolRegistry.cs +++ b/Jint/Native/Symbol/GlobalSymbolRegistry.cs @@ -43,4 +43,22 @@ internal bool ContainsCustom(JsValue value) { return value is JsSymbol symbol && _customSymbolLookup?.ContainsKey(symbol._value) == true; } + + internal JsValue KeyForSymbol(JsSymbol symbol) + { + if (_customSymbolLookup == null) + { + return JsValue.Undefined; + } + + foreach (var pair in _customSymbolLookup) + { + if (pair.Value == symbol) + { + return pair.Key; + } + } + + return JsValue.Undefined; + } } diff --git a/Jint/Native/WeakMap/WeakMapPrototype.cs b/Jint/Native/WeakMap/WeakMapPrototype.cs index 78e9de1e6d..5796a86fb4 100644 --- a/Jint/Native/WeakMap/WeakMapPrototype.cs +++ b/Jint/Native/WeakMap/WeakMapPrototype.cs @@ -27,15 +27,17 @@ internal WeakMapPrototype( protected override void Initialize() { - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - var properties = new PropertyDictionary(6, checkExistingKeys: false) + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + var properties = new PropertyDictionary(8, checkExistingKeys: false) { ["length"] = new PropertyDescriptor(0, PropertyFlag.Configurable), ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable), - ["delete"] = new PropertyDescriptor(new ClrFunction(Engine, "delete", Delete, 1, PropertyFlag.Configurable), propertyFlags), - ["get"] = new PropertyDescriptor(new ClrFunction(Engine, "get", Get, 1, PropertyFlag.Configurable), propertyFlags), - ["has"] = new PropertyDescriptor(new ClrFunction(Engine, "has", Has, 1, PropertyFlag.Configurable), propertyFlags), - ["set"] = new PropertyDescriptor(new ClrFunction(Engine, "set", Set, 2, PropertyFlag.Configurable), propertyFlags), + ["delete"] = new PropertyDescriptor(new ClrFunction(Engine, "delete", Delete, 1, PropertyFlag.Configurable), PropertyFlags), + ["get"] = new PropertyDescriptor(new ClrFunction(Engine, "get", Get, 1, PropertyFlag.Configurable), PropertyFlags), + ["getOrInsert"] = new PropertyDescriptor(new ClrFunction(Engine, "getOrInsert", GetOrInsert, 2, PropertyFlag.Configurable), PropertyFlags), + ["getOrInsertComputed"] = new PropertyDescriptor(new ClrFunction(Engine, "getOrInsertComputed", GetOrInsertComputed, 2, PropertyFlag.Configurable), PropertyFlags), + ["has"] = new PropertyDescriptor(new ClrFunction(Engine, "has", Has, 1, PropertyFlag.Configurable), PropertyFlags), + ["set"] = new PropertyDescriptor(new ClrFunction(Engine, "set", Set, 2, PropertyFlag.Configurable), PropertyFlags), }; SetProperties(properties); @@ -52,6 +54,32 @@ private JsValue Get(JsValue thisObject, JsCallArguments arguments) return map.WeakMapGet(arguments.At(0)); } + private JsValue GetOrInsert(JsValue thisObject, JsCallArguments arguments) + { + var map = AssertWeakMapInstance(thisObject); + var key = AssertCanBeHeldWeakly(arguments.At(0)); + var value = arguments.At(1); + return map.GetOrInsert(key, value); + } + + private JsValue GetOrInsertComputed(JsValue thisObject, JsCallArguments arguments) + { + var map = AssertWeakMapInstance(thisObject); + var key = AssertCanBeHeldWeakly(arguments.At(0)); + var callbackfn = arguments.At(1).GetCallable(_realm); + return map.GetOrInsertComputed(key, callbackfn); + } + + private JsValue AssertCanBeHeldWeakly(JsValue key) + { + if (!key.CanBeHeldWeakly(_engine)) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + return key; + } + private JsValue Delete(JsValue thisObject, JsCallArguments arguments) { var map = AssertWeakMapInstance(thisObject); diff --git a/README.md b/README.md index 0e5dbd8bb5..b6c2ee689f 100644 --- a/README.md +++ b/README.md @@ -140,12 +140,13 @@ and many more. - ❌ Duplicate named capture groups - ✔ Set methods (`intersection`, `union`, `difference`, `symmetricDifference`, `isSubsetOf`, `isSupersetOf`, `isDisjointFrom`) -#### ECMAScript Stage 3 (no version yet) +#### ECMAScript Stage 3 or earlier (no version yet) - ✔ `Error.isError` - ✔ `Math.sumPrecise` - ✔ `ShadowRealm` - ✔ `Uint8Array` to/from base64 +- ✔ `Upsert` #### Other