From d82b3b81f1dc4518cb5c3ab1a97049a7638eb679 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 19 Apr 2026 10:37:41 +0200 Subject: [PATCH 1/7] add assignMany, concat, and concatMany for Dict --- packages/@rescript/runtime/Stdlib_Dict.res | 7 + packages/@rescript/runtime/Stdlib_Dict.resi | 63 ++++++++- tests/tests/src/stdlib/Stdlib_DictTests.mjs | 146 +++++++++++++++++++- tests/tests/src/stdlib/Stdlib_DictTests.res | 78 +++++++++++ 4 files changed, 286 insertions(+), 8 deletions(-) diff --git a/packages/@rescript/runtime/Stdlib_Dict.res b/packages/@rescript/runtime/Stdlib_Dict.res index 82e9978874..4ca880919b 100644 --- a/packages/@rescript/runtime/Stdlib_Dict.res +++ b/packages/@rescript/runtime/Stdlib_Dict.res @@ -23,6 +23,13 @@ external fromIterable: Stdlib_Iterable.t<(string, 'a)> => dict<'a> = "Object.fro @val external assign: (dict<'a>, dict<'a>) => dict<'a> = "Object.assign" +@variadic @val external assignMany: (dict<'a>, array>) => dict<'a> = "Object.assign" + +@val external concat: (@as(json`{}`) _, dict<'a>, dict<'a>) => dict<'a> = "Object.assign" + +@variadic @val +external concatMany: (@as(json`{}`) _, dict<'a>, array>) => dict<'a> = "Object.assign" + @val external copy: (@as(json`{}`) _, dict<'a>) => dict<'a> = "Object.assign" // Use %raw to support for..in which is a ~10% faster than .forEach diff --git a/packages/@rescript/runtime/Stdlib_Dict.resi b/packages/@rescript/runtime/Stdlib_Dict.resi index 198f547275..954bbde84a 100644 --- a/packages/@rescript/runtime/Stdlib_Dict.resi +++ b/packages/@rescript/runtime/Stdlib_Dict.resi @@ -222,7 +222,7 @@ external valuesToArray: dict<'a> => array<'a> = "Object.values" /** `assign(dictionary1, dictionary2)` [shallowly](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) merges dictionary2 into dictionary1, and returns dictionary1. -Beware this will *mutate* dictionary1. If you're looking for a way to copy a dictionary, check out `Dict.copy`. +Beware this will *mutate* dictionary1. If you're looking for a fresh merged dictionary instead, check out `Dict.concat`. ## Examples ```rescript @@ -242,6 +242,67 @@ Console.log(dict1->Dict.keysToArray) // Logs `["firstKey", "someKey", "someKey2" @val external assign: (dict<'a>, dict<'a>) => dict<'a> = "Object.assign" +/** +`assignMany(target, sources)` [shallowly](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) merges each dictionary in `sources` into `target`, and returns `target`. + +Beware this will *mutate* `target`. Later dictionaries overwrite earlier ones. If you're looking for a fresh merged dictionary instead, check out `Dict.concatMany`. + +## Examples +```rescript +let target = dict{"firstKey": 1} +let result = target->Dict.assignMany([ + dict{"someKey": 2}, + dict{"someKey": 3, "someKey2": 4}, +]) + +assertEqual(result, dict{"firstKey": 1, "someKey": 3, "someKey2": 4}) +assertEqual(result === target, true) +``` +*/ +@variadic @val +external assignMany: (dict<'a>, array>) => dict<'a> = "Object.assign" + +/** +`concat(dictionary1, dictionary2)` [shallowly](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) merges `dictionary1` and `dictionary2` into a fresh dictionary, and returns the new dictionary. + +Neither input dictionary is mutated. If both dictionaries contain the same key, the value from `dictionary2` overwrites the one from `dictionary1`. + +## Examples +```rescript +let dict1 = dict{"firstKey": 1} +let dict2 = dict{"firstKey": 2, "someKey": 3} + +let merged = dict1->Dict.concat(dict2) + +assertEqual(dict1, dict{"firstKey": 1}) +assertEqual(merged, dict{"firstKey": 2, "someKey": 3}) +assertEqual(merged === dict1, false) +``` +*/ +@val +external concat: (@as(json`{}`) _, dict<'a>, dict<'a>) => dict<'a> = "Object.assign" + +/** +`concatMany(target, sources)` [shallowly](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) merges `target` and each dictionary in `sources` into a fresh dictionary, and returns the new dictionary. + +Neither `target` nor any dictionary in `sources` is mutated. Later dictionaries overwrite earlier ones when they contain the same key. + +## Examples +```rescript +let target = dict{"firstKey": 1} +let merged = target->Dict.concatMany([ + dict{"someKey": 2}, + dict{"someKey": 3, "someKey2": 4}, +]) + +assertEqual(target, dict{"firstKey": 1}) +assertEqual(merged, dict{"firstKey": 1, "someKey": 3, "someKey2": 4}) +assertEqual(merged === target, false) +``` +*/ +@variadic @val +external concatMany: (@as(json`{}`) _, dict<'a>, array>) => dict<'a> = "Object.assign" + /** `copy(dictionary)` [shallowly copies](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) the provided dictionary to a new dictionary. diff --git a/tests/tests/src/stdlib/Stdlib_DictTests.mjs b/tests/tests/src/stdlib/Stdlib_DictTests.mjs index 717c265756..8d810e4ae1 100644 --- a/tests/tests/src/stdlib/Stdlib_DictTests.mjs +++ b/tests/tests/src/stdlib/Stdlib_DictTests.mjs @@ -87,10 +87,142 @@ Test.run([ ] ]), eq, {foo: "bar", baz: "qux"}); +let target = { + a: 1, + b: 2 +}; + +let result = Object.assign(target, { + b: 3 +}, { + b: 4, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 58, + 15, + 57 + ], + "assignMany copies from sources to target" +], result, eq, { + a: 1, + b: 4, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 67, + 22, + 49 + ], + "assignMany mutates target" +], result === target, eq, true); + +let target$1 = { + a: 1, + b: 2 +}; + +let result$1 = Object.assign({}, target$1, { + b: 3, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 78, + 15, + 54 + ], + "concat copies into a fresh dictionary" +], result$1, eq, { + a: 1, + b: 3, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 88, + 15, + 47 + ], + "concat leaves target unchanged" +], target$1, eq, { + a: 1, + b: 2 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 96, + 22, + 57 + ], + "concat returns a fresh dictionary" +], result$1 === target$1, eq, false); + +let target$2 = { + a: 1, + b: 2 +}; + +let result$2 = Object.assign({}, target$2, { + b: 3 +}, { + b: 4, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 107, + 15, + 58 + ], + "concatMany copies into a fresh dictionary" +], result$2, eq, { + a: 1, + b: 4, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 117, + 15, + 51 + ], + "concatMany leaves target unchanged" +], target$2, eq, { + a: 1, + b: 2 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 125, + 22, + 61 + ], + "concatMany returns a fresh dictionary" +], result$2 === target$2, eq, false); + Test.run([ [ "Stdlib_DictTests.res", - 51, + 129, 13, 35 ], @@ -103,7 +235,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 57, + 135, 13, 34 ], @@ -118,7 +250,7 @@ let dict = { Test.run([ [ "Stdlib_DictTests.res", - 69, + 147, 22, 38 ], @@ -128,7 +260,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 70, + 148, 22, 43 ], @@ -138,7 +270,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 71, + 149, 22, 37 ], @@ -148,7 +280,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 72, + 150, 22, 39 ], @@ -158,7 +290,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 74, + 152, 15, 51 ], diff --git a/tests/tests/src/stdlib/Stdlib_DictTests.res b/tests/tests/src/stdlib/Stdlib_DictTests.res index 4e171cb7c0..df357be7e6 100644 --- a/tests/tests/src/stdlib/Stdlib_DictTests.res +++ b/tests/tests/src/stdlib/Stdlib_DictTests.res @@ -47,6 +47,84 @@ Test.run( %raw(`{foo: "bar", baz: "qux"}`), ) +{ + let target = dict{ + "a": 1, + "b": 2, + } + let result = target->Dict.assignMany([dict{"b": 3}, dict{"b": 4, "c": 0}]) + + Test.run( + __POS_OF__("assignMany copies from sources to target"), + result, + eq, + dict{ + "a": 1, + "b": 4, + "c": 0, + }, + ) + Test.run(__POS_OF__("assignMany mutates target"), result === target, eq, true) +} + +{ + let target = dict{ + "a": 1, + "b": 2, + } + let result = target->Dict.concat(dict{"b": 3, "c": 0}) + + Test.run( + __POS_OF__("concat copies into a fresh dictionary"), + result, + eq, + dict{ + "a": 1, + "b": 3, + "c": 0, + }, + ) + Test.run( + __POS_OF__("concat leaves target unchanged"), + target, + eq, + dict{ + "a": 1, + "b": 2, + }, + ) + Test.run(__POS_OF__("concat returns a fresh dictionary"), result === target, eq, false) +} + +{ + let target = dict{ + "a": 1, + "b": 2, + } + let result = target->Dict.concatMany([dict{"b": 3}, dict{"b": 4, "c": 0}]) + + Test.run( + __POS_OF__("concatMany copies into a fresh dictionary"), + result, + eq, + dict{ + "a": 1, + "b": 4, + "c": 0, + }, + ) + Test.run( + __POS_OF__("concatMany leaves target unchanged"), + target, + eq, + dict{ + "a": 1, + "b": 2, + }, + ) + Test.run(__POS_OF__("concatMany returns a fresh dictionary"), result === target, eq, false) +} + Test.run( __POS_OF__("getUnsafe - existing"), Dict.fromArray([("foo", "bar")])->Dict.getUnsafe("foo"), From ec4a9f35694297d2bfe1a492cc400ac159b6451d Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 19 Apr 2026 10:43:00 +0200 Subject: [PATCH 2/7] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 314e08424e..74156a699e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ #### :rocket: New Feature - Rewatch: add `--prod` flag to `build`, `watch`, and `clean` to skip dev-dependencies and dev sources (`"type": "dev"`), enabling builds in environments where dev packages aren't installed (e.g. after `pnpm install --prod`). https://github.com/rescript-lang/rescript/pull/8347 +- Add `Dict.assignMany`, `Dict.concat` and `Dict.concatMany` to the stdlib. https://github.com/rescript-lang/rescript/pull/8364 #### :bug: Bug fix From 58d9b4f5dac5fe00d63f2e4229f5a2875efb7760 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 19 Apr 2026 11:23:47 +0200 Subject: [PATCH 3/7] add Dict.concatAll --- packages/@rescript/runtime/Stdlib_Dict.res | 2 + packages/@rescript/runtime/Stdlib_Dict.resi | 20 +++ tests/tests/src/stdlib/Stdlib_DictTests.mjs | 171 +++++++++++++++++++- tests/tests/src/stdlib/Stdlib_DictTests.res | 99 ++++++++++++ 4 files changed, 285 insertions(+), 7 deletions(-) diff --git a/packages/@rescript/runtime/Stdlib_Dict.res b/packages/@rescript/runtime/Stdlib_Dict.res index 4ca880919b..04f1b56281 100644 --- a/packages/@rescript/runtime/Stdlib_Dict.res +++ b/packages/@rescript/runtime/Stdlib_Dict.res @@ -30,6 +30,8 @@ external fromIterable: Stdlib_Iterable.t<(string, 'a)> => dict<'a> = "Object.fro @variadic @val external concatMany: (@as(json`{}`) _, dict<'a>, array>) => dict<'a> = "Object.assign" +@variadic @val external concatAll: (@as(json`{}`) _, array>) => dict<'a> = "Object.assign" + @val external copy: (@as(json`{}`) _, dict<'a>) => dict<'a> = "Object.assign" // Use %raw to support for..in which is a ~10% faster than .forEach diff --git a/packages/@rescript/runtime/Stdlib_Dict.resi b/packages/@rescript/runtime/Stdlib_Dict.resi index 954bbde84a..9dfbb8c966 100644 --- a/packages/@rescript/runtime/Stdlib_Dict.resi +++ b/packages/@rescript/runtime/Stdlib_Dict.resi @@ -303,6 +303,26 @@ assertEqual(merged === target, false) @variadic @val external concatMany: (@as(json`{}`) _, dict<'a>, array>) => dict<'a> = "Object.assign" +/** +`concatAll(dictionaries)` [shallowly](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) merges all dictionaries in `dictionaries` into a fresh dictionary, and returns the new dictionary. + +None of the input dictionaries are mutated. Later dictionaries overwrite earlier ones when they contain the same key. If `dictionaries` is empty, an empty dictionary is returned. + +## Examples +```rescript +let dict1 = dict{"firstKey": 1} +let dict2 = dict{"someKey": 2} +let dict3 = dict{"someKey": 3, "someKey2": 4} + +let merged = Dict.concatAll([dict1, dict2, dict3]) + +assertEqual(dict1, dict{"firstKey": 1}) +assertEqual(merged, dict{"firstKey": 1, "someKey": 3, "someKey2": 4}) +``` +*/ +@variadic @val +external concatAll: (@as(json`{}`) _, array>) => dict<'a> = "Object.assign" + /** `copy(dictionary)` [shallowly copies](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) the provided dictionary to a new dictionary. diff --git a/tests/tests/src/stdlib/Stdlib_DictTests.mjs b/tests/tests/src/stdlib/Stdlib_DictTests.mjs index 8d810e4ae1..2c887ac966 100644 --- a/tests/tests/src/stdlib/Stdlib_DictTests.mjs +++ b/tests/tests/src/stdlib/Stdlib_DictTests.mjs @@ -219,10 +219,167 @@ Test.run([ "concatMany returns a fresh dictionary" ], result$2 === target$2, eq, false); +let first = { + a: 1, + b: 2 +}; + +let last = { + b: 4, + c: 0 +}; + +let result$3 = Object.assign({}, first, { + b: 3 +}, last); + +Test.run([ + [ + "Stdlib_DictTests.res", + 140, + 15, + 57 + ], + "concatAll copies into a fresh dictionary" +], result$3, eq, { + a: 1, + b: 4, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 150, + 15, + 56 + ], + "concatAll leaves first source unchanged" +], first, eq, { + a: 1, + b: 2 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 159, + 15, + 56 + ], + "concatAll leaves later source unchanged" +], last, eq, { + b: 4, + c: 0 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 167, + 22, + 60 + ], + "concatAll returns a fresh dictionary" +], result$3 === first, eq, false); + +Test.run([ + [ + "Stdlib_DictTests.res", + 171, + 13, + 66 + ], + "concatAll with empty array returns empty dictionary" +], Object.assign({}), eq, {}); + +let foo = { + a: 1, + b: 2 +}; + +let baz = { + b: 4, + c: 5 +}; + +let result$4 = Object.assign({}, foo, { + b: 3 +}, baz, { + d: 6 +}); + Test.run([ [ "Stdlib_DictTests.res", - 129, + 189, + 15, + 53 + ], + "dict spread respects overwrite order" +], result$4, eq, { + a: 1, + b: 4, + c: 5, + d: 6 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 200, + 15, + 58 + ], + "dict spread leaves first source unchanged" +], foo, eq, { + a: 1, + b: 2 +}); + +Test.run([ + [ + "Stdlib_DictTests.res", + 209, + 15, + 58 + ], + "dict spread leaves later source unchanged" +], baz, eq, { + b: 4, + c: 5 +}); + +let foo$1 = { + a: 1 +}; + +let result$5 = Object.assign({}, foo$1); + +Test.run([ + [ + "Stdlib_DictTests.res", + 223, + 22, + 55 + ], + "dict spread clone copies values" +], result$5, eq, foo$1); + +Test.run([ + [ + "Stdlib_DictTests.res", + 224, + 22, + 68 + ], + "dict spread clone returns a fresh dictionary" +], result$5 === foo$1, eq, false); + +Test.run([ + [ + "Stdlib_DictTests.res", + 228, 13, 35 ], @@ -235,7 +392,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 135, + 234, 13, 34 ], @@ -250,7 +407,7 @@ let dict = { Test.run([ [ "Stdlib_DictTests.res", - 147, + 246, 22, 38 ], @@ -260,7 +417,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 148, + 247, 22, 43 ], @@ -270,7 +427,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 149, + 248, 22, 37 ], @@ -280,7 +437,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 150, + 249, 22, 39 ], @@ -290,7 +447,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 152, + 251, 15, 51 ], diff --git a/tests/tests/src/stdlib/Stdlib_DictTests.res b/tests/tests/src/stdlib/Stdlib_DictTests.res index df357be7e6..13f517a9b0 100644 --- a/tests/tests/src/stdlib/Stdlib_DictTests.res +++ b/tests/tests/src/stdlib/Stdlib_DictTests.res @@ -125,6 +125,105 @@ Test.run( Test.run(__POS_OF__("concatMany returns a fresh dictionary"), result === target, eq, false) } +{ + let first = dict{ + "a": 1, + "b": 2, + } + let last = dict{ + "b": 4, + "c": 0, + } + let result = Dict.concatAll([first, dict{"b": 3}, last]) + + Test.run( + __POS_OF__("concatAll copies into a fresh dictionary"), + result, + eq, + dict{ + "a": 1, + "b": 4, + "c": 0, + }, + ) + Test.run( + __POS_OF__("concatAll leaves first source unchanged"), + first, + eq, + dict{ + "a": 1, + "b": 2, + }, + ) + Test.run( + __POS_OF__("concatAll leaves later source unchanged"), + last, + eq, + dict{ + "b": 4, + "c": 0, + }, + ) + Test.run(__POS_OF__("concatAll returns a fresh dictionary"), result === first, eq, false) +} + +Test.run( + __POS_OF__("concatAll with empty array returns empty dictionary"), + Dict.concatAll([]), + eq, + dict{}, +) + +{ + let foo = dict{ + "a": 1, + "b": 2, + } + let baz = dict{ + "b": 4, + "c": 5, + } + let result = dict{...foo, "b": 3, ...baz, "d": 6} + + Test.run( + __POS_OF__("dict spread respects overwrite order"), + result, + eq, + dict{ + "a": 1, + "b": 4, + "c": 5, + "d": 6, + }, + ) + Test.run( + __POS_OF__("dict spread leaves first source unchanged"), + foo, + eq, + dict{ + "a": 1, + "b": 2, + }, + ) + Test.run( + __POS_OF__("dict spread leaves later source unchanged"), + baz, + eq, + dict{ + "b": 4, + "c": 5, + }, + ) +} + +{ + let foo = dict{"a": 1} + let result = dict{...foo} + + Test.run(__POS_OF__("dict spread clone copies values"), result, eq, foo) + Test.run(__POS_OF__("dict spread clone returns a fresh dictionary"), result === foo, eq, false) +} + Test.run( __POS_OF__("getUnsafe - existing"), Dict.fromArray([("foo", "bar")])->Dict.getUnsafe("foo"), From eb60f7d546731cf5a28167e54734a914074ffdeb Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 19 Apr 2026 11:32:28 +0200 Subject: [PATCH 4/7] add Array.concatAll --- CHANGELOG.md | 2 +- packages/@rescript/runtime/Stdlib_Array.res | 1 + packages/@rescript/runtime/Stdlib_Array.resi | 17 +++++ .../runtime/lib/es6/Stdlib_Array.mjs | 5 ++ .../@rescript/runtime/lib/js/Stdlib_Array.cjs | 5 ++ tests/tests/src/stdlib/Stdlib_ArrayTests.mjs | 76 +++++++++++++++++-- tests/tests/src/stdlib/Stdlib_ArrayTests.res | 15 ++++ 7 files changed, 112 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74156a699e..d07ebaba51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ #### :rocket: New Feature - Rewatch: add `--prod` flag to `build`, `watch`, and `clean` to skip dev-dependencies and dev sources (`"type": "dev"`), enabling builds in environments where dev packages aren't installed (e.g. after `pnpm install --prod`). https://github.com/rescript-lang/rescript/pull/8347 -- Add `Dict.assignMany`, `Dict.concat` and `Dict.concatMany` to the stdlib. https://github.com/rescript-lang/rescript/pull/8364 +- Add `Dict.assignMany`, `Dict.concat`, `Dict.concatMany`, `Dict.concatAll`, `Array.concatAll` to the stdlib. https://github.com/rescript-lang/rescript/pull/8364 #### :bug: Bug fix diff --git a/packages/@rescript/runtime/Stdlib_Array.res b/packages/@rescript/runtime/Stdlib_Array.res index 71ea2057e0..eae6ac01bc 100644 --- a/packages/@rescript/runtime/Stdlib_Array.res +++ b/packages/@rescript/runtime/Stdlib_Array.res @@ -145,6 +145,7 @@ external removeInPlace: (array<'a>, int, @as(1) _) => unit = "splice" @send external concat: (array<'a>, array<'a>) => array<'a> = "concat" @variadic @send external concatMany: (array<'a>, array>) => array<'a> = "concat" +let concatAll = arrays => concatMany([], arrays) @send external flat: array> => array<'a> = "flat" diff --git a/packages/@rescript/runtime/Stdlib_Array.resi b/packages/@rescript/runtime/Stdlib_Array.resi index 3a2196ec37..9367d294fc 100644 --- a/packages/@rescript/runtime/Stdlib_Array.resi +++ b/packages/@rescript/runtime/Stdlib_Array.resi @@ -577,6 +577,23 @@ Console.log(someArray) // ["hi", "hello", "yay", "wehoo"] @variadic @send external concatMany: (array<'a>, array>) => array<'a> = "concat" +/** +`concatAll(arrays)` concatenates all arrays in `arrays`, creating a new array. + +See [`Array.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) on MDN. + +## Examples +```rescript +let arrays = [["hi", "hello"], ["yay"], ["wehoo"]] + +let result = Array.concatAll(arrays) + +assertEqual(arrays, [["hi", "hello"], ["yay"], ["wehoo"]]) +assertEqual(result, ["hi", "hello", "yay", "wehoo"]) +``` +*/ +let concatAll: array> => array<'a> + /** `flat(arrays)` concatenates an array of arrays into a single array. diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_Array.mjs b/packages/@rescript/runtime/lib/es6/Stdlib_Array.mjs index c93b796b76..2bdb3d434b 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib_Array.mjs +++ b/packages/@rescript/runtime/lib/es6/Stdlib_Array.mjs @@ -71,6 +71,10 @@ function compare(a, b, cmp) { } } +function concatAll(arrays) { + return [].concat(...arrays); +} + function indexOfOpt(arr, item) { let index = arr.indexOf(item); if (index !== -1) { @@ -264,6 +268,7 @@ export { equal, compare, isEmpty, + concatAll, indexOfOpt, lastIndexOfOpt, reduce, diff --git a/packages/@rescript/runtime/lib/js/Stdlib_Array.cjs b/packages/@rescript/runtime/lib/js/Stdlib_Array.cjs index 5d91437978..d82695753d 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib_Array.cjs +++ b/packages/@rescript/runtime/lib/js/Stdlib_Array.cjs @@ -71,6 +71,10 @@ function compare(a, b, cmp) { } } +function concatAll(arrays) { + return [].concat(...arrays); +} + function indexOfOpt(arr, item) { let index = arr.indexOf(item); if (index !== -1) { @@ -263,6 +267,7 @@ exports.fromInitializer = fromInitializer; exports.equal = equal; exports.compare = compare; exports.isEmpty = isEmpty; +exports.concatAll = concatAll; exports.indexOfOpt = indexOfOpt; exports.lastIndexOfOpt = lastIndexOfOpt; exports.reduce = reduce; diff --git a/tests/tests/src/stdlib/Stdlib_ArrayTests.mjs b/tests/tests/src/stdlib/Stdlib_ArrayTests.mjs index f78059dd22..6c55617f70 100644 --- a/tests/tests/src/stdlib/Stdlib_ArrayTests.mjs +++ b/tests/tests/src/stdlib/Stdlib_ArrayTests.mjs @@ -443,10 +443,70 @@ Test.run([ "c" ]); +let arrays = [ + [ + 1, + 2 + ], + [3], + [ + 4, + 5 + ] +]; + +let result = Stdlib_Array.concatAll(arrays); + +Test.run([ + [ + "Stdlib_ArrayTests.res", + 121, + 22, + 53 + ], + "concatAll concatenates arrays" +], result, eq, [ + 1, + 2, + 3, + 4, + 5 +]); + +Test.run([ + [ + "Stdlib_ArrayTests.res", + 123, + 15, + 57 + ], + "concatAll leaves source arrays unchanged" +], arrays, eq, [ + [ + 1, + 2 + ], + [3], + [ + 4, + 5 + ] +]); + +Test.run([ + [ + "Stdlib_ArrayTests.res", + 130, + 20, + 39 + ], + "concatAll - empty" +], Stdlib_Array.concatAll([]), eq, []); + Test.run([ [ "Stdlib_ArrayTests.res", - 118, + 133, 13, 31 ], @@ -465,7 +525,7 @@ Test.run([ Test.run([ [ "Stdlib_ArrayTests.res", - 125, + 140, 13, 31 ], @@ -479,7 +539,7 @@ Test.run([ Test.run([ [ "Stdlib_ArrayTests.res", - 132, + 147, 13, 29 ], @@ -507,7 +567,7 @@ Test.run([ Test.run([ [ "Stdlib_ArrayTests.res", - 139, + 154, 13, 29 ], @@ -524,7 +584,7 @@ Test.run([ Test.run([ [ "Stdlib_ArrayTests.res", - 145, + 160, 20, 39 ], @@ -538,7 +598,7 @@ Test.run([ Test.run([ [ "Stdlib_ArrayTests.res", - 146, + 161, 20, 34 ], @@ -552,7 +612,7 @@ array.splice(1, 0, "foo"); Test.run([ [ "Stdlib_ArrayTests.res", - 151, + 166, 22, 49 ], @@ -567,7 +627,7 @@ let array$1 = [ Test.run([ [ "Stdlib_ArrayTests.res", - 157, + 172, 15, 43 ], diff --git a/tests/tests/src/stdlib/Stdlib_ArrayTests.res b/tests/tests/src/stdlib/Stdlib_ArrayTests.res index cf9d671885..6d85c3229d 100644 --- a/tests/tests/src/stdlib/Stdlib_ArrayTests.res +++ b/tests/tests/src/stdlib/Stdlib_ArrayTests.res @@ -114,6 +114,21 @@ Test.run( ["a", "b", "c"], ) +{ + let arrays = [[1, 2], [3], [4, 5]] + let result = Array.concatAll(arrays) + + Test.run(__POS_OF__("concatAll concatenates arrays"), result, eq, [1, 2, 3, 4, 5]) + Test.run( + __POS_OF__("concatAll leaves source arrays unchanged"), + arrays, + eq, + [[1, 2], [3], [4, 5]], + ) +} + +Test.run(__POS_OF__("concatAll - empty"), Array.concatAll([]), eq, []) + Test.run( __POS_OF__("Map.fromIterable"), Map.fromIterable([("one", 1), ("two", 2)]->Array.asIterable)->Map.size, From 527bf9b95fad19c5790306dc787488bc51b9172f Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 19 Apr 2026 11:37:53 +0200 Subject: [PATCH 5/7] remove dict spread tests --- tests/tests/src/stdlib/Stdlib_DictTests.mjs | 97 ++------------------- tests/tests/src/stdlib/Stdlib_DictTests.res | 50 ----------- 2 files changed, 7 insertions(+), 140 deletions(-) diff --git a/tests/tests/src/stdlib/Stdlib_DictTests.mjs b/tests/tests/src/stdlib/Stdlib_DictTests.mjs index 2c887ac966..a51a827bd6 100644 --- a/tests/tests/src/stdlib/Stdlib_DictTests.mjs +++ b/tests/tests/src/stdlib/Stdlib_DictTests.mjs @@ -293,93 +293,10 @@ Test.run([ "concatAll with empty array returns empty dictionary" ], Object.assign({}), eq, {}); -let foo = { - a: 1, - b: 2 -}; - -let baz = { - b: 4, - c: 5 -}; - -let result$4 = Object.assign({}, foo, { - b: 3 -}, baz, { - d: 6 -}); - -Test.run([ - [ - "Stdlib_DictTests.res", - 189, - 15, - 53 - ], - "dict spread respects overwrite order" -], result$4, eq, { - a: 1, - b: 4, - c: 5, - d: 6 -}); - -Test.run([ - [ - "Stdlib_DictTests.res", - 200, - 15, - 58 - ], - "dict spread leaves first source unchanged" -], foo, eq, { - a: 1, - b: 2 -}); - -Test.run([ - [ - "Stdlib_DictTests.res", - 209, - 15, - 58 - ], - "dict spread leaves later source unchanged" -], baz, eq, { - b: 4, - c: 5 -}); - -let foo$1 = { - a: 1 -}; - -let result$5 = Object.assign({}, foo$1); - -Test.run([ - [ - "Stdlib_DictTests.res", - 223, - 22, - 55 - ], - "dict spread clone copies values" -], result$5, eq, foo$1); - -Test.run([ - [ - "Stdlib_DictTests.res", - 224, - 22, - 68 - ], - "dict spread clone returns a fresh dictionary" -], result$5 === foo$1, eq, false); - Test.run([ [ "Stdlib_DictTests.res", - 228, + 178, 13, 35 ], @@ -392,7 +309,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 234, + 184, 13, 34 ], @@ -407,7 +324,7 @@ let dict = { Test.run([ [ "Stdlib_DictTests.res", - 246, + 196, 22, 38 ], @@ -417,7 +334,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 247, + 197, 22, 43 ], @@ -427,7 +344,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 248, + 198, 22, 37 ], @@ -437,7 +354,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 249, + 199, 22, 39 ], @@ -447,7 +364,7 @@ Test.run([ Test.run([ [ "Stdlib_DictTests.res", - 251, + 201, 15, 51 ], diff --git a/tests/tests/src/stdlib/Stdlib_DictTests.res b/tests/tests/src/stdlib/Stdlib_DictTests.res index 13f517a9b0..9ec803edfb 100644 --- a/tests/tests/src/stdlib/Stdlib_DictTests.res +++ b/tests/tests/src/stdlib/Stdlib_DictTests.res @@ -174,56 +174,6 @@ Test.run( dict{}, ) -{ - let foo = dict{ - "a": 1, - "b": 2, - } - let baz = dict{ - "b": 4, - "c": 5, - } - let result = dict{...foo, "b": 3, ...baz, "d": 6} - - Test.run( - __POS_OF__("dict spread respects overwrite order"), - result, - eq, - dict{ - "a": 1, - "b": 4, - "c": 5, - "d": 6, - }, - ) - Test.run( - __POS_OF__("dict spread leaves first source unchanged"), - foo, - eq, - dict{ - "a": 1, - "b": 2, - }, - ) - Test.run( - __POS_OF__("dict spread leaves later source unchanged"), - baz, - eq, - dict{ - "b": 4, - "c": 5, - }, - ) -} - -{ - let foo = dict{"a": 1} - let result = dict{...foo} - - Test.run(__POS_OF__("dict spread clone copies values"), result, eq, foo) - Test.run(__POS_OF__("dict spread clone returns a fresh dictionary"), result === foo, eq, false) -} - Test.run( __POS_OF__("getUnsafe - existing"), Dict.fromArray([("foo", "bar")])->Dict.getUnsafe("foo"), From 05d3f0897c78fbb7af9d6fb552991676e349d4e7 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 19 Apr 2026 11:41:52 +0200 Subject: [PATCH 6/7] update test output --- tests/analysis_tests/tests/src/expected/Completion.res.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index 5254a5f459..5fb5a8e3ec 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -490,6 +490,12 @@ Path Array. "tags": [], "detail": "(array<'a>, array<'a>, ('a, 'a) => bool) => bool", "documentation": {"kind": "markdown", "value": "\n`equal(left, right, predicate)` checks if the two arrays contain the same elements according to the equality `predicate`.\n\n## Examples\n\n```rescript\nArray.equal([1, 2, 3], [1, 2, 3], Int.equal) == true\nArray.equal([1, 2, 3], [1, 3, 2], Int.equal) == false\n```\n"} + }, { + "label": "concatAll", + "kind": 12, + "tags": [], + "detail": "array> => array<'a>", + "documentation": {"kind": "markdown", "value": "\n`concatAll(arrays)` concatenates all arrays in `arrays`, creating a new array.\n\nSee [`Array.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) on MDN.\n\n## Examples\n```rescript\nlet arrays = [[\"hi\", \"hello\"], [\"yay\"], [\"wehoo\"]]\n\nlet result = Array.concatAll(arrays)\n\nassertEqual(arrays, [[\"hi\", \"hello\"], [\"yay\"], [\"wehoo\"]])\nassertEqual(result, [\"hi\", \"hello\", \"yay\", \"wehoo\"])\n```\n"} }, { "label": "joinUnsafe", "kind": 12, From fdbe74e8eb3f05cdee3deb9d81a75e8e6df6f884 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 19 Apr 2026 12:20:19 +0200 Subject: [PATCH 7/7] fix docstring tests --- packages/@rescript/runtime/Stdlib_Array.resi | 4 ++-- packages/@rescript/runtime/Stdlib_Dict.resi | 21 ++++++++++--------- .../tests/src/expected/Completion.res.txt | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/@rescript/runtime/Stdlib_Array.resi b/packages/@rescript/runtime/Stdlib_Array.resi index 9367d294fc..45ca887b6f 100644 --- a/packages/@rescript/runtime/Stdlib_Array.resi +++ b/packages/@rescript/runtime/Stdlib_Array.resi @@ -588,8 +588,8 @@ let arrays = [["hi", "hello"], ["yay"], ["wehoo"]] let result = Array.concatAll(arrays) -assertEqual(arrays, [["hi", "hello"], ["yay"], ["wehoo"]]) -assertEqual(result, ["hi", "hello", "yay", "wehoo"]) +arrays == [["hi", "hello"], ["yay"], ["wehoo"]] +result == ["hi", "hello", "yay", "wehoo"] ``` */ let concatAll: array> => array<'a> diff --git a/packages/@rescript/runtime/Stdlib_Dict.resi b/packages/@rescript/runtime/Stdlib_Dict.resi index 9dfbb8c966..224be3ffef 100644 --- a/packages/@rescript/runtime/Stdlib_Dict.resi +++ b/packages/@rescript/runtime/Stdlib_Dict.resi @@ -255,8 +255,8 @@ let result = target->Dict.assignMany([ dict{"someKey": 3, "someKey2": 4}, ]) -assertEqual(result, dict{"firstKey": 1, "someKey": 3, "someKey2": 4}) -assertEqual(result === target, true) +result == dict{"firstKey": 1, "someKey": 3, "someKey2": 4} +(result === target) == true ``` */ @variadic @val @@ -274,9 +274,9 @@ let dict2 = dict{"firstKey": 2, "someKey": 3} let merged = dict1->Dict.concat(dict2) -assertEqual(dict1, dict{"firstKey": 1}) -assertEqual(merged, dict{"firstKey": 2, "someKey": 3}) -assertEqual(merged === dict1, false) +dict1 == dict{"firstKey": 1} +merged == dict{"firstKey": 2, "someKey": 3} +(merged === dict1) == false ``` */ @val @@ -295,9 +295,9 @@ let merged = target->Dict.concatMany([ dict{"someKey": 3, "someKey2": 4}, ]) -assertEqual(target, dict{"firstKey": 1}) -assertEqual(merged, dict{"firstKey": 1, "someKey": 3, "someKey2": 4}) -assertEqual(merged === target, false) +target == dict{"firstKey": 1} +merged == dict{"firstKey": 1, "someKey": 3, "someKey2": 4} +(merged === target) == false ``` */ @variadic @val @@ -316,8 +316,9 @@ let dict3 = dict{"someKey": 3, "someKey2": 4} let merged = Dict.concatAll([dict1, dict2, dict3]) -assertEqual(dict1, dict{"firstKey": 1}) -assertEqual(merged, dict{"firstKey": 1, "someKey": 3, "someKey2": 4}) +dict1 == dict{"firstKey": 1} +merged == dict{"firstKey": 1, "someKey": 3, "someKey2": 4} +(merged === dict1) == false ``` */ @variadic @val diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index 5fb5a8e3ec..3014be92e3 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -495,7 +495,7 @@ Path Array. "kind": 12, "tags": [], "detail": "array> => array<'a>", - "documentation": {"kind": "markdown", "value": "\n`concatAll(arrays)` concatenates all arrays in `arrays`, creating a new array.\n\nSee [`Array.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) on MDN.\n\n## Examples\n```rescript\nlet arrays = [[\"hi\", \"hello\"], [\"yay\"], [\"wehoo\"]]\n\nlet result = Array.concatAll(arrays)\n\nassertEqual(arrays, [[\"hi\", \"hello\"], [\"yay\"], [\"wehoo\"]])\nassertEqual(result, [\"hi\", \"hello\", \"yay\", \"wehoo\"])\n```\n"} + "documentation": {"kind": "markdown", "value": "\n`concatAll(arrays)` concatenates all arrays in `arrays`, creating a new array.\n\nSee [`Array.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) on MDN.\n\n## Examples\n```rescript\nlet arrays = [[\"hi\", \"hello\"], [\"yay\"], [\"wehoo\"]]\n\nlet result = Array.concatAll(arrays)\n\narrays == [[\"hi\", \"hello\"], [\"yay\"], [\"wehoo\"]]\nresult == [\"hi\", \"hello\", \"yay\", \"wehoo\"]\n```\n"} }, { "label": "joinUnsafe", "kind": 12,