luau/bench/other/LuauPolyfillMap.lua
Arseny Kapoulkine 2460e38998
bench: Implement luau-analyze and luau --compile benchmarks (#575)
This change adds another file for benchmarking luau-analyze and sets up
benchmarks for both non-strict/strict modes for analysis and all three
optimization levels for compilation performance.

To avoid issues with race conditions on repository update we do all this
in the same job in benchmark.yml.

To be able to benchmark both modes from a single file, luau-analyze
gains --mode argument which allows to override the default typechecking
mode. Not sure if we'll want this to be a hard override on top of the
module-specified mode in the future, but this works for now.
2022-07-05 08:23:09 -07:00

962 lines
24 KiB
Lua

-- This file is part of the Roblox luau-polyfill repository and is licensed under MIT License; see LICENSE.txt for details
-- #region Array
-- Array related
local Array = {}
local Object = {}
local Map = {}
type Array<T> = { [number]: T }
type callbackFn<K, V> = (element: V, key: K, map: Map<K, V>) -> ()
type callbackFnWithThisArg<K, V> = (thisArg: Object, value: V, key: K, map: Map<K, V>) -> ()
type Map<K, V> = {
size: number,
-- method definitions
set: (self: Map<K, V>, K, V) -> Map<K, V>,
get: (self: Map<K, V>, K) -> V | nil,
clear: (self: Map<K, V>) -> (),
delete: (self: Map<K, V>, K) -> boolean,
forEach: (self: Map<K, V>, callback: callbackFn<K, V> | callbackFnWithThisArg<K, V>, thisArg: Object?) -> (),
has: (self: Map<K, V>, K) -> boolean,
keys: (self: Map<K, V>) -> Array<K>,
values: (self: Map<K, V>) -> Array<V>,
entries: (self: Map<K, V>) -> Array<Tuple<K, V>>,
ipairs: (self: Map<K, V>) -> any,
[K]: V,
_map: { [K]: V },
_array: { [number]: K },
}
type mapFn<T, U> = (element: T, index: number) -> U
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U
type Object = { [string]: any }
type Table<T, V> = { [T]: V }
type Tuple<T, V> = Array<T | V>
local Set = {}
-- #region Array
function Array.isArray(value: any): boolean
if typeof(value) ~= "table" then
return false
end
if next(value) == nil then
-- an empty table is an empty array
return true
end
local length = #value
if length == 0 then
return false
end
local count = 0
local sum = 0
for key in pairs(value) do
if typeof(key) ~= "number" then
return false
end
if key % 1 ~= 0 or key < 1 then
return false
end
count += 1
sum += key
end
return sum == (count * (count + 1) / 2)
end
function Array.from<T, U>(
value: string | Array<T> | Object,
mapFn: (mapFn<T, U> | mapFnWithThisArg<T, U>)?,
thisArg: Object?
): Array<U>
if value == nil then
error("cannot create array from a nil value")
end
local valueType = typeof(value)
local array = {}
if valueType == "table" and Array.isArray(value) then
if mapFn then
for i = 1, #(value :: Array<T>) do
if thisArg ~= nil then
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, (value :: Array<T>)[i], i)
else
array[i] = (mapFn :: mapFn<T, U>)((value :: Array<T>)[i], i)
end
end
else
for i = 1, #(value :: Array<T>) do
array[i] = (value :: Array<any>)[i]
end
end
elseif instanceOf(value, Set) then
if mapFn then
for i, v in (value :: any):ipairs() do
if thisArg ~= nil then
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
else
array[i] = (mapFn :: mapFn<T, U>)(v, i)
end
end
else
for i, v in (value :: any):ipairs() do
array[i] = v
end
end
elseif instanceOf(value, Map) then
if mapFn then
for i, v in (value :: any):ipairs() do
if thisArg ~= nil then
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
else
array[i] = (mapFn :: mapFn<T, U>)(v, i)
end
end
else
for i, v in (value :: any):ipairs() do
array[i] = v
end
end
elseif valueType == "string" then
if mapFn then
for i = 1, (value :: string):len() do
if thisArg ~= nil then
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, (value :: any):sub(i, i), i)
else
array[i] = (mapFn :: mapFn<T, U>)((value :: any):sub(i, i), i)
end
end
else
for i = 1, (value :: string):len() do
array[i] = (value :: any):sub(i, i)
end
end
end
return array
end
type callbackFnArrayMap<T, U> = (element: T, index: number, array: Array<T>) -> U
type callbackFnWithThisArgArrayMap<T, U, V> = (thisArg: V, element: T, index: number, array: Array<T>) -> U
-- Implements Javascript's `Array.prototype.map` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
function Array.map<T, U, V>(
t: Array<T>,
callback: callbackFnArrayMap<T, U> | callbackFnWithThisArgArrayMap<T, U, V>,
thisArg: V?
): Array<U>
if typeof(t) ~= "table" then
error(string.format("Array.map called on %s", typeof(t)))
end
if typeof(callback) ~= "function" then
error("callback is not a function")
end
local len = #t
local A = {}
local k = 1
while k <= len do
local kValue = t[k]
if kValue ~= nil then
local mappedValue
if thisArg ~= nil then
mappedValue = (callback :: callbackFnWithThisArgArrayMap<T, U, V>)(thisArg, kValue, k, t)
else
mappedValue = (callback :: callbackFnArrayMap<T, U>)(kValue, k, t)
end
A[k] = mappedValue
end
k += 1
end
return A
end
type Function = (any, any, number, any) -> any
-- Implements Javascript's `Array.prototype.reduce` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
function Array.reduce<T>(array: Array<T>, callback: Function, initialValue: any?): any
if typeof(array) ~= "table" then
error(string.format("Array.reduce called on %s", typeof(array)))
end
if typeof(callback) ~= "function" then
error("callback is not a function")
end
local length = #array
local value
local initial = 1
if initialValue ~= nil then
value = initialValue
else
initial = 2
if length == 0 then
error("reduce of empty array with no initial value")
end
value = array[1]
end
for i = initial, length do
value = callback(value, array[i], i, array)
end
return value
end
type callbackFnArrayForEach<T> = (element: T, index: number, array: Array<T>) -> ()
type callbackFnWithThisArgArrayForEach<T, U> = (thisArg: U, element: T, index: number, array: Array<T>) -> ()
-- Implements Javascript's `Array.prototype.forEach` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
function Array.forEach<T, U>(
t: Array<T>,
callback: callbackFnArrayForEach<T> | callbackFnWithThisArgArrayForEach<T, U>,
thisArg: U?
): ()
if typeof(t) ~= "table" then
error(string.format("Array.forEach called on %s", typeof(t)))
end
if typeof(callback) ~= "function" then
error("callback is not a function")
end
local len = #t
local k = 1
while k <= len do
local kValue = t[k]
if thisArg ~= nil then
(callback :: callbackFnWithThisArgArrayForEach<T, U>)(thisArg, kValue, k, t)
else
(callback :: callbackFnArrayForEach<T>)(kValue, k, t)
end
if #t < len then
-- don't iterate on removed items, don't iterate more than original length
len = #t
end
k += 1
end
end
-- #endregion
-- #region Set
Set.__index = Set
type callbackFnSet<T> = (value: T, key: T, set: Set<T>) -> ()
type callbackFnWithThisArgSet<T> = (thisArg: Object, value: T, key: T, set: Set<T>) -> ()
export type Set<T> = {
size: number,
-- method definitions
add: (self: Set<T>, T) -> Set<T>,
clear: (self: Set<T>) -> (),
delete: (self: Set<T>, T) -> boolean,
forEach: (self: Set<T>, callback: callbackFnSet<T> | callbackFnWithThisArgSet<T>, thisArg: Object?) -> (),
has: (self: Set<T>, T) -> boolean,
ipairs: (self: Set<T>) -> any,
}
type Iterable = { ipairs: (any) -> any }
function Set.new<T>(iterable: Array<T> | Set<T> | Iterable | string | nil): Set<T>
local array = {}
local map = {}
if iterable ~= nil then
local arrayIterable: Array<any>
-- ROBLOX TODO: remove type casting from (iterable :: any).ipairs in next release
if typeof(iterable) == "table" then
if Array.isArray(iterable) then
arrayIterable = Array.from(iterable :: Array<any>)
elseif typeof((iterable :: Iterable).ipairs) == "function" then
-- handle in loop below
elseif _G.__DEV__ then
error("cannot create array from an object-like table")
end
elseif typeof(iterable) == "string" then
arrayIterable = Array.from(iterable :: string)
else
error(("cannot create array from value of type `%s`"):format(typeof(iterable)))
end
if arrayIterable then
for _, element in ipairs(arrayIterable) do
if not map[element] then
map[element] = true
table.insert(array, element)
end
end
elseif typeof(iterable) == "table" and typeof((iterable :: Iterable).ipairs) == "function" then
for _, element in (iterable :: Iterable):ipairs() do
if not map[element] then
map[element] = true
table.insert(array, element)
end
end
end
end
return (setmetatable({
size = #array,
_map = map,
_array = array,
}, Set) :: any) :: Set<T>
end
function Set:add(value)
if not self._map[value] then
-- Luau FIXME: analyze should know self is Set<T> which includes size as a number
self.size = self.size :: number + 1
self._map[value] = true
table.insert(self._array, value)
end
return self
end
function Set:clear()
self.size = 0
table.clear(self._map)
table.clear(self._array)
end
function Set:delete(value): boolean
if not self._map[value] then
return false
end
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
self.size = self.size :: number - 1
self._map[value] = nil
local index = table.find(self._array, value)
if index then
table.remove(self._array, index)
end
return true
end
-- Implements Javascript's `Map.prototype.forEach` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach
function Set:forEach<T>(callback: callbackFnSet<T> | callbackFnWithThisArgSet<T>, thisArg: Object?): ()
if typeof(callback) ~= "function" then
error("callback is not a function")
end
return Array.forEach(self._array, function(value: T)
if thisArg ~= nil then
(callback :: callbackFnWithThisArgSet<T>)(thisArg, value, value, self)
else
(callback :: callbackFnSet<T>)(value, value, self)
end
end)
end
function Set:has(value): boolean
return self._map[value] ~= nil
end
function Set:ipairs()
return ipairs(self._array)
end
-- #endregion Set
-- #region Object
function Object.entries(value: string | Object | Array<any>): Array<any>
assert(value :: any ~= nil, "cannot get entries from a nil value")
local valueType = typeof(value)
local entries: Array<Tuple<string, any>> = {}
if valueType == "table" then
for key, keyValue in pairs(value :: Object) do
-- Luau FIXME: Luau should see entries as Array<any>, given object is [string]: any, but it sees it as Array<Array<string>> despite all the manual annotation
table.insert(entries, { key :: string, keyValue :: any })
end
elseif valueType == "string" then
for i = 1, string.len(value :: string) do
entries[i] = { tostring(i), string.sub(value :: string, i, i) }
end
end
return entries
end
-- #endregion
-- #region instanceOf
-- ROBLOX note: Typed tbl as any to work with strict type analyze
-- polyfill for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
function instanceOf(tbl: any, class)
assert(typeof(class) == "table", "Received a non-table as the second argument for instanceof")
if typeof(tbl) ~= "table" then
return false
end
local ok, hasNew = pcall(function()
return class.new ~= nil and tbl.new == class.new
end)
if ok and hasNew then
return true
end
local seen = { tbl = true }
while tbl and typeof(tbl) == "table" do
tbl = getmetatable(tbl)
if typeof(tbl) == "table" then
tbl = tbl.__index
if tbl == class then
return true
end
end
-- if we still have a valid table then check against seen
if typeof(tbl) == "table" then
if seen[tbl] then
return false
end
seen[tbl] = true
end
end
return false
end
-- #endregion
function Map.new<K, V>(iterable: Array<Array<any>>?): Map<K, V>
local array = {}
local map = {}
if iterable ~= nil then
local arrayFromIterable
local iterableType = typeof(iterable)
if iterableType == "table" then
if #iterable > 0 and typeof(iterable[1]) ~= "table" then
error("cannot create Map from {K, V} form, it must be { {K, V}... }")
end
arrayFromIterable = Array.from(iterable)
else
error(("cannot create array from value of type `%s`"):format(iterableType))
end
for _, entry in ipairs(arrayFromIterable) do
local key = entry[1]
if _G.__DEV__ then
if key == nil then
error("cannot create Map from a table that isn't an array.")
end
end
local val = entry[2]
-- only add to array if new
if map[key] == nil then
table.insert(array, key)
end
-- always assign
map[key] = val
end
end
return (setmetatable({
size = #array,
_map = map,
_array = array,
}, Map) :: any) :: Map<K, V>
end
function Map:set<K, V>(key: K, value: V): Map<K, V>
-- preserve initial insertion order
if self._map[key] == nil then
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
self.size = self.size :: number + 1
table.insert(self._array, key)
end
-- always update value
self._map[key] = value
return self
end
function Map:get(key)
return self._map[key]
end
function Map:clear()
local table_: any = table
self.size = 0
table_.clear(self._map)
table_.clear(self._array)
end
function Map:delete(key): boolean
if self._map[key] == nil then
return false
end
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
self.size = self.size :: number - 1
self._map[key] = nil
local index = table.find(self._array, key)
if index then
table.remove(self._array, index)
end
return true
end
-- Implements Javascript's `Map.prototype.forEach` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
function Map:forEach<K, V>(callback: callbackFn<K, V> | callbackFnWithThisArg<K, V>, thisArg: Object?): ()
if typeof(callback) ~= "function" then
error("callback is not a function")
end
return Array.forEach(self._array, function(key: K)
local value: V = self._map[key] :: V
if thisArg ~= nil then
(callback :: callbackFnWithThisArg<K, V>)(thisArg, value, key, self)
else
(callback :: callbackFn<K, V>)(value, key, self)
end
end)
end
function Map:has(key): boolean
return self._map[key] ~= nil
end
function Map:keys()
return self._array
end
function Map:values()
return Array.map(self._array, function(key)
return self._map[key]
end)
end
function Map:entries()
return Array.map(self._array, function(key)
return { key, self._map[key] }
end)
end
function Map:ipairs()
return ipairs(self:entries())
end
function Map.__index(self, key)
local mapProp = rawget(Map, key)
if mapProp ~= nil then
return mapProp
end
return Map.get(self, key)
end
function Map.__newindex(table_, key, value)
table_:set(key, value)
end
local function coerceToMap(mapLike: Map<any, any> | Table<any, any>): Map<any, any>
return instanceOf(mapLike, Map) and mapLike :: Map<any, any> -- ROBLOX: order is preservered
or Map.new(Object.entries(mapLike)) -- ROBLOX: order is not preserved
end
-- local function coerceToTable(mapLike: Map<any, any> | Table<any, any>): Table<any, any>
-- if not instanceOf(mapLike, Map) then
-- return mapLike
-- end
-- -- create table from map
-- return Array.reduce(mapLike:entries(), function(tbl, entry)
-- tbl[entry[1]] = entry[2]
-- return tbl
-- end, {})
-- end
-- #region Tests to verify it works as expected
local function it(description: string, fn: () -> ())
local ok, result = pcall(fn)
if not ok then
error("Failed test: " .. description .. "\n" .. result)
end
end
local AN_ITEM = "bar"
local ANOTHER_ITEM = "baz"
-- #region [Describe] "Map"
-- #region [Child Describe] "constructors"
it("creates an empty array", function()
local foo = Map.new()
assert(foo.size == 0)
end)
it("creates a Map from an array", function()
local foo = Map.new({
{ AN_ITEM, "foo" },
{ ANOTHER_ITEM, "val" },
})
assert(foo.size == 2)
assert(foo:has(AN_ITEM) == true)
assert(foo:has(ANOTHER_ITEM) == true)
end)
it("creates a Map from an array with duplicate keys", function()
local foo = Map.new({
{ AN_ITEM, "foo1" },
{ AN_ITEM, "foo2" },
})
assert(foo.size == 1)
assert(foo:get(AN_ITEM) == "foo2")
assert(#foo:keys() == 1 and foo:keys()[1] == AN_ITEM)
assert(#foo:values() == 1 and foo:values()[1] == "foo2")
assert(#foo:entries() == 1)
assert(#foo:entries()[1] == 2)
assert(foo:entries()[1][1] == AN_ITEM)
assert(foo:entries()[1][2] == "foo2")
end)
it("preserves the order of keys first assignment", function()
local foo = Map.new({
{ AN_ITEM, "foo1" },
{ ANOTHER_ITEM, "bar" },
{ AN_ITEM, "foo2" },
})
assert(foo.size == 2)
assert(foo:get(AN_ITEM) == "foo2")
assert(foo:get(ANOTHER_ITEM) == "bar")
assert(foo:keys()[1] == AN_ITEM)
assert(foo:keys()[2] == ANOTHER_ITEM)
assert(foo:values()[1] == "foo2")
assert(foo:values()[2] == "bar")
assert(foo:entries()[1][1] == AN_ITEM)
assert(foo:entries()[1][2] == "foo2")
assert(foo:entries()[2][1] == ANOTHER_ITEM)
assert(foo:entries()[2][2] == "bar")
end)
-- #endregion
-- #region [Child Describe] "type"
it("instanceOf return true for an actual Map object", function()
local foo = Map.new()
assert(instanceOf(foo, Map) == true)
end)
it("instanceOf return false for an regular plain object", function()
local foo = {}
assert(instanceOf(foo, Map) == false)
end)
-- #endregion
-- #region [Child Describe] "set"
it("returns the Map object", function()
local foo = Map.new()
assert(foo:set(1, "baz") == foo)
end)
it("increments the size if the element is added for the first time", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
assert(foo.size == 1)
end)
it("does not increment the size the second time an element is added", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:set(AN_ITEM, "val")
assert(foo.size == 1)
end)
it("sets values correctly to true/false", function()
-- Luau FIXME: Luau insists that arrays can't be mixed type
local foo = Map.new({ { AN_ITEM, false :: any } })
foo:set(AN_ITEM, false)
assert(foo.size == 1)
assert(foo:get(AN_ITEM) == false)
foo:set(AN_ITEM, true)
assert(foo.size == 1)
assert(foo:get(AN_ITEM) == true)
foo:set(AN_ITEM, false)
assert(foo.size == 1)
assert(foo:get(AN_ITEM) == false)
end)
-- #endregion
-- #region [Child Describe] "get"
it("returns value of item from provided key", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
assert(foo:get(AN_ITEM) == "foo")
end)
it("returns nil if the item is not in the Map", function()
local foo = Map.new()
assert(foo:get(AN_ITEM) == nil)
end)
-- #endregion
-- #region [Child Describe] "clear"
it("sets the size to zero", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:clear()
assert(foo.size == 0)
end)
it("removes the items from the Map", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:clear()
assert(foo:has(AN_ITEM) == false)
end)
-- #endregion
-- #region [Child Describe] "delete"
it("removes the items from the Map", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:delete(AN_ITEM)
assert(foo:has(AN_ITEM) == false)
end)
it("returns true if the item was in the Map", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
assert(foo:delete(AN_ITEM) == true)
end)
it("returns false if the item was not in the Map", function()
local foo = Map.new()
assert(foo:delete(AN_ITEM) == false)
end)
it("decrements the size if the item was in the Map", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:delete(AN_ITEM)
assert(foo.size == 0)
end)
it("does not decrement the size if the item was not in the Map", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:delete(ANOTHER_ITEM)
assert(foo.size == 1)
end)
it("deletes value set to false", function()
-- Luau FIXME: Luau insists arrays can't be mixed type
local foo = Map.new({ { AN_ITEM, false :: any } })
foo:delete(AN_ITEM)
assert(foo.size == 0)
assert(foo:get(AN_ITEM) == nil)
end)
-- #endregion
-- #region [Child Describe] "has"
it("returns true if the item is in the Map", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
assert(foo:has(AN_ITEM) == true)
end)
it("returns false if the item is not in the Map", function()
local foo = Map.new()
assert(foo:has(AN_ITEM) == false)
end)
it("returns correctly with value set to false", function()
-- Luau FIXME: Luau insists arrays can't be mixed type
local foo = Map.new({ { AN_ITEM, false :: any } })
assert(foo:has(AN_ITEM) == true)
end)
-- #endregion
-- #region [Child Describe] "keys / values / entries"
it("returns array of elements", function()
local myMap = Map.new()
myMap:set(AN_ITEM, "foo")
myMap:set(ANOTHER_ITEM, "val")
assert(myMap:keys()[1] == AN_ITEM)
assert(myMap:keys()[2] == ANOTHER_ITEM)
assert(myMap:values()[1] == "foo")
assert(myMap:values()[2] == "val")
assert(myMap:entries()[1][1] == AN_ITEM)
assert(myMap:entries()[1][2] == "foo")
assert(myMap:entries()[2][1] == ANOTHER_ITEM)
assert(myMap:entries()[2][2] == "val")
end)
-- #endregion
-- #region [Child Describe] "__index"
it("can access fields directly without using get", function()
local typeName = "size"
local foo = Map.new({
{ AN_ITEM, "foo" },
{ ANOTHER_ITEM, "val" },
{ typeName, "buzz" },
})
assert(foo.size == 3)
assert(foo[AN_ITEM] == "foo")
assert(foo[ANOTHER_ITEM] == "val")
assert(foo:get(typeName) == "buzz")
end)
-- #endregion
-- #region [Child Describe] "__newindex"
it("can set fields directly without using set", function()
local foo = Map.new()
assert(foo.size == 0)
foo[AN_ITEM] = "foo"
foo[ANOTHER_ITEM] = "val"
foo.fizz = "buzz"
assert(foo.size == 3)
assert(foo:get(AN_ITEM) == "foo")
assert(foo:get(ANOTHER_ITEM) == "val")
assert(foo:get("fizz") == "buzz")
end)
-- #endregion
-- #region [Child Describe] "ipairs"
local function makeArray(...)
local array = {}
for _, item in ... do
table.insert(array, item)
end
return array
end
it("iterates on the elements by their insertion order", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:set(ANOTHER_ITEM, "val")
assert(makeArray(foo:ipairs())[1][1] == AN_ITEM)
assert(makeArray(foo:ipairs())[1][2] == "foo")
assert(makeArray(foo:ipairs())[2][1] == ANOTHER_ITEM)
assert(makeArray(foo:ipairs())[2][2] == "val")
end)
it("does not iterate on removed elements", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:set(ANOTHER_ITEM, "val")
foo:delete(AN_ITEM)
assert(makeArray(foo:ipairs())[1][1] == ANOTHER_ITEM)
assert(makeArray(foo:ipairs())[1][2] == "val")
end)
it("iterates on elements if the added back to the Map", function()
local foo = Map.new()
foo:set(AN_ITEM, "foo")
foo:set(ANOTHER_ITEM, "val")
foo:delete(AN_ITEM)
foo:set(AN_ITEM, "food")
assert(makeArray(foo:ipairs())[1][1] == ANOTHER_ITEM)
assert(makeArray(foo:ipairs())[1][2] == "val")
assert(makeArray(foo:ipairs())[2][1] == AN_ITEM)
assert(makeArray(foo:ipairs())[2][2] == "food")
end)
-- #endregion
-- #region [Child Describe] "Integration Tests"
-- it("MDN Examples", function()
-- local myMap = Map.new() :: Map<string | Object | Function, string>
-- local keyString = "a string"
-- local keyObj = {}
-- local keyFunc = function() end
-- -- setting the values
-- myMap:set(keyString, "value associated with 'a string'")
-- myMap:set(keyObj, "value associated with keyObj")
-- myMap:set(keyFunc, "value associated with keyFunc")
-- assert(myMap.size == 3)
-- -- getting the values
-- assert(myMap:get(keyString) == "value associated with 'a string'")
-- assert(myMap:get(keyObj) == "value associated with keyObj")
-- assert(myMap:get(keyFunc) == "value associated with keyFunc")
-- assert(myMap:get("a string") == "value associated with 'a string'")
-- assert(myMap:get({}) == nil) -- nil, because keyObj !== {}
-- assert(myMap:get(function() -- nil because keyFunc !== function () {}
-- end) == nil)
-- end)
it("handles non-traditional keys", function()
local myMap = Map.new() :: Map<boolean | number | string, string>
local falseKey = false
local trueKey = true
local negativeKey = -1
local emptyKey = ""
myMap:set(falseKey, "apple")
myMap:set(trueKey, "bear")
myMap:set(negativeKey, "corgi")
myMap:set(emptyKey, "doge")
assert(myMap.size == 4)
assert(myMap:get(falseKey) == "apple")
assert(myMap:get(trueKey) == "bear")
assert(myMap:get(negativeKey) == "corgi")
assert(myMap:get(emptyKey) == "doge")
myMap:delete(falseKey)
myMap:delete(trueKey)
myMap:delete(negativeKey)
myMap:delete(emptyKey)
assert(myMap.size == 0)
end)
-- #endregion
-- #endregion [Describe] "Map"
-- #region [Describe] "coerceToMap"
it("returns the same object if instance of Map", function()
local map = Map.new()
assert(coerceToMap(map) == map)
map = Map.new({})
assert(coerceToMap(map) == map)
map = Map.new({ { AN_ITEM, "foo" } })
assert(coerceToMap(map) == map)
end)
-- #endregion [Describe] "coerceToMap"
-- #endregion Tests to verify it works as expected