Browse Source

Implement Upsert (#2127)

Marko Lahma 4 weeks ago
parent
commit
1a6c1274d3

+ 1 - 2
Jint.Tests.Test262/Test262Harness.settings.json

@@ -22,8 +22,7 @@
     "source-phase-imports",
     "source-phase-imports",
     "tail-call-optimization",
     "tail-call-optimization",
     "Temporal",
     "Temporal",
-    "u180e",
-    "upsert"
+    "u180e"
   ],
   ],
   "ExcludedFlags": [
   "ExcludedFlags": [
   ],
   ],

+ 26 - 1
Jint/JsValueExtensions.cs

@@ -27,7 +27,6 @@ public static class JsValueExtensions
         return value._type == InternalTypes.Undefined;
         return value._type == InternalTypes.Undefined;
     }
     }
 
 
-
     [Pure]
     [Pure]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static bool IsArray(this JsValue value)
     public static bool IsArray(this JsValue value)
@@ -738,4 +737,30 @@ public static class JsValueExtensions
 
 
         return TypeConverter.ToIndex(oi.Engine.Realm, maxByteLength);
         return TypeConverter.ToIndex(oi.Engine.Realm, maxByteLength);
     }
     }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-canonicalize-keyed-collection-key
+    /// </summary>
+    internal static JsValue CanonicalizeKeyedCollectionKey(this JsValue key)
+    {
+        return key is JsNumber number && number.IsNegativeZero() ? JsNumber.PositiveZero : key;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-canbeheldweakly
+    /// </summary>
+    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;
+    }
 }
 }

+ 24 - 0
Jint/Native/JsMap.cs

@@ -56,6 +56,30 @@ public sealed class JsMap : ObjectInstance, IEnumerable<KeyValuePair<JsValue, Js
         return value;
         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)
     public new void Set(JsValue key, JsValue value)
     {
     {
         if (key is JsNumber number && number.IsNegativeZero())
         if (key is JsNumber number && number.IsNegativeZero())

+ 23 - 0
Jint/Native/JsWeakMap.cs

@@ -48,4 +48,27 @@ internal sealed class JsWeakMap : ObjectInstance
         return value;
         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;
+    }
 }
 }

+ 30 - 12
Jint/Native/Map/MapPrototype.cs

@@ -27,27 +27,29 @@ internal sealed class MapPrototype : Prototype
 
 
     protected override void Initialize()
     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),
             ["length"] = new PropertyDescriptor(0, PropertyFlag.Configurable),
             ["constructor"] = new PropertyDescriptor(_mapConstructor, PropertyFlag.NonEnumerable),
             ["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)
             ["size"] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get size", Size, 0, PropertyFlag.Configurable), set: null, PropertyFlag.Configurable)
         };
         };
         SetProperties(properties);
         SetProperties(properties);
 
 
         var symbols = new SymbolDictionary(2)
         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),
             [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("Map", false, false, true),
         };
         };
         SetSymbols(symbols);
         SetSymbols(symbols);
@@ -65,6 +67,22 @@ internal sealed class MapPrototype : Prototype
         return map.Get(arguments.At(0));
         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)
     private JsValue Clear(JsValue thisObject, JsCallArguments arguments)
     {
     {
         var map = AssertMapInstance(thisObject);
         var map = AssertMapInstance(thisObject);

+ 18 - 0
Jint/Native/Symbol/GlobalSymbolRegistry.cs

@@ -43,4 +43,22 @@ public sealed class GlobalSymbolRegistry
     {
     {
         return value is JsSymbol symbol && _customSymbolLookup?.ContainsKey(symbol._value) == true;
         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;
+    }
 }
 }

+ 34 - 6
Jint/Native/WeakMap/WeakMapPrototype.cs

@@ -27,15 +27,17 @@ internal sealed class WeakMapPrototype : Prototype
 
 
     protected override void Initialize()
     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),
             ["length"] = new PropertyDescriptor(0, PropertyFlag.Configurable),
             ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
             ["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);
         SetProperties(properties);
 
 
@@ -52,6 +54,32 @@ internal sealed class WeakMapPrototype : Prototype
         return map.WeakMapGet(arguments.At(0));
         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)
     private JsValue Delete(JsValue thisObject, JsCallArguments arguments)
     {
     {
         var map = AssertWeakMapInstance(thisObject);
         var map = AssertWeakMapInstance(thisObject);

+ 2 - 1
README.md

@@ -140,12 +140,13 @@ and many more.
 - ❌ Duplicate named capture groups
 - ❌ Duplicate named capture groups
 - ✔ Set methods (`intersection`, `union`, `difference`, `symmetricDifference`, `isSubsetOf`, `isSupersetOf`, `isDisjointFrom`)
 - ✔ 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`
 - ✔ `Error.isError`
 - ✔ `Math.sumPrecise`
 - ✔ `Math.sumPrecise`
 - ✔ `ShadowRealm`
 - ✔ `ShadowRealm`
 - ✔ `Uint8Array` to/from base64
 - ✔ `Uint8Array` to/from base64
+- ✔ `Upsert`
 
 
 #### Other
 #### Other