diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfeac60ea1..45b2d296c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -478,7 +478,8 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version-file: .nvmrc + # Run integration tests with the oldest supported node version. + node-version: 20 - name: Make test directory id: tmp-dir diff --git a/.nvmrc b/.nvmrc index 209e3ef4b6..2bd5a0a98a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7069eb2161..ccefc24c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Add `Array.findLast`, `Array.findLastWithIndex`, `Array.findLastIndex`, `Array.findLastIndexWithIndex` and `Array.findLastIndexOpt`. https://github.com/rescript-lang/rescript/pull/7503 - Add `options` argument to `Console.dir`. https://github.com/rescript-lang/rescript/pull/7504 - Show variant constructor's inline record types on hover. https://github.com/rescript-lang/rescript/pull/7519 +- Add additional `Iterator.prototype` bindings to `runtime/Stdlib_Iterator.res`. https://github.com/rescript-lang/rescript/pull/7506 #### :bug: Bug fix @@ -34,6 +35,9 @@ - Remove deprecated pipe last (`|>`) syntax. https://github.com/rescript-lang/rescript/pull/7512 - Improve error message for pipe (`->`) syntax. https://github.com/rescript-lang/rescript/pull/7520 +#### :boom: Breaking Change +- `Iterator.forEach` now emits `Iterator.prototype.forEach` call. https://github.com/rescript-lang/rescript/pull/7506 + # 12.0.0-alpha.13 #### :boom: Breaking Change diff --git a/lib/es6/Stdlib_Iterator.js b/lib/es6/Stdlib_Iterator.js index e793b51c42..ae1b9f17e6 100644 --- a/lib/es6/Stdlib_Iterator.js +++ b/lib/es6/Stdlib_Iterator.js @@ -1,16 +1 @@ - - - -function forEach(iterator, f) { - let iteratorDone = false; - while (!iteratorDone) { - let match = iterator.next(); - f(match.value); - iteratorDone = match.done; - }; -} - -export { - forEach, -} -/* No side effect */ +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/lib/js/Stdlib_Iterator.js b/lib/js/Stdlib_Iterator.js index e696f08b1a..ae1b9f17e6 100644 --- a/lib/js/Stdlib_Iterator.js +++ b/lib/js/Stdlib_Iterator.js @@ -1,14 +1 @@ -'use strict'; - - -function forEach(iterator, f) { - let iteratorDone = false; - while (!iteratorDone) { - let match = iterator.next(); - f(match.value); - iteratorDone = match.done; - }; -} - -exports.forEach = forEach; -/* No side effect */ +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/runtime/Stdlib_Array.res b/runtime/Stdlib_Array.res index a88869f9a5..32a9979b8f 100644 --- a/runtime/Stdlib_Array.res +++ b/runtime/Stdlib_Array.res @@ -287,3 +287,9 @@ let findMap = (arr, f) => { let last = a => a->get(a->length - 1) external ignore: array<'a> => unit = "%ignore" + +@send +external entries: array<'a> => Stdlib_Iterator.t<(int, 'a)> = "entries" + +@send +external values: array<'a> => Stdlib_Iterator.t<'a> = "values" diff --git a/runtime/Stdlib_Array.resi b/runtime/Stdlib_Array.resi index fff252d987..c48d90af13 100644 --- a/runtime/Stdlib_Array.resi +++ b/runtime/Stdlib_Array.resi @@ -1387,3 +1387,37 @@ let last: array<'a> => option<'a> without having to store or process it further. */ external ignore: array<'a> => unit = "%ignore" + +/** +`entries(array)` returns a new array iterator object that contains the key/value pairs for each index in the array. + +See [Array.prototype.entries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries) on MDN. + +## Examples + +```rescript +let array = [5, 6, 7] +let iterator : Iterator.t<(int, int)> = array->Array.entries +iterator->Iterator.next->assertEqual({done: false, value: Some((0, 5))}) +iterator->Iterator.next->assertEqual({done: false, value: Some((1, 6))}) +``` +*/ +@send +external entries: array<'a> => Stdlib_Iterator.t<(int, 'a)> = "entries" + +/** +`values(array)` returns a new array iterator object that contains the values for each index in the array. + +See [Array.prototype.values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values) on MDN. + +## Examples + +```rescript +let array = [5, 6, 7] +let iterator : Iterator.t = array->Array.values +iterator->Iterator.next->assertEqual({done: false, value: Some(5)}) +iterator->Iterator.next->assertEqual({done: false, value: Some(6)}) +``` + */ +@send +external values: array<'a> => Stdlib_Iterator.t<'a> = "values" diff --git a/runtime/Stdlib_Iterator.res b/runtime/Stdlib_Iterator.res index 01d14aa5a1..1ef310d355 100644 --- a/runtime/Stdlib_Iterator.res +++ b/runtime/Stdlib_Iterator.res @@ -7,17 +7,38 @@ type value<'a> = { } @send external next: t<'a> => value<'a> = "next" -external toArray: t<'a> => array<'a> = "Array.from" +@send +external toArray: t<'a> => array<'a> = "toArray" external toArrayWithMapper: (t<'a>, 'a => 'b) => array<'b> = "Array.from" -let forEach = (iterator, f) => { - let iteratorDone = ref(false) - - while !iteratorDone.contents { - let {done, value} = iterator->next - f(value) - iteratorDone := done - } -} +@send +external forEach: (t<'a>, 'a => unit) => unit = "forEach" external ignore: t<'a> => unit = "%ignore" + +@send +external drop: (t<'a>, int) => t<'a> = "drop" + +@send +external every: (t<'a>, 'a => bool) => bool = "every" + +@send +external filter: (t<'a>, 'a => bool) => t<'a> = "filter" + +@send +external find: (t<'a>, 'a => bool) => option<'a> = "find" + +@send +external flatMap: (t<'a>, 'a => t<'b>) => t<'b> = "flatMap" + +@send +external map: (t<'a>, 'a => 'b) => t<'b> = "map" + +@send +external reduce: (t<'a>, ('acc, 'a) => 'acc, ~initialValue: 'acc=?) => 'acc = "reduce" + +@send +external some: (t<'a>, 'a => bool) => bool = "some" + +@send +external take: (t<'a>, int) => t<'a> = "take" diff --git a/runtime/Stdlib_Iterator.resi b/runtime/Stdlib_Iterator.resi index 91e43fe00e..2181aa0c1e 100644 --- a/runtime/Stdlib_Iterator.resi +++ b/runtime/Stdlib_Iterator.resi @@ -1,7 +1,7 @@ /*** Bindings to JavaScript iterators. -See [`iterator protocols`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) on MDN. +See [`Iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) on MDN. */ /** @@ -50,7 +50,7 @@ external next: t<'a> => value<'a> = "next" Turns an iterator into an array of the remaining values. Remember that each invocation of `next` of an iterator consumes a value. `Iterator.toArray` will consume all remaining values of the iterator and return them in an array to you. -See [iterator protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) on MDN. +See [Iterator.prototype.toArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/toArray) on MDN. ## Examples ```rescript @@ -61,16 +61,17 @@ map->Map.set("someKey2", "someValue2") // `Map.keys` returns all keys of the map as an iterator. let mapKeysAsArray = map->Map.keys->Iterator.toArray -Console.log(mapKeysAsArray) // Logs ["someKey", "someKey2"] to the console. +mapKeysAsArray->assertEqual(["someKey", "someKey2"]) ``` */ -external toArray: t<'a> => array<'a> = "Array.from" +@send +external toArray: t<'a> => array<'a> = "toArray" /** `toArray(iterator)` turns `iterator` into an array of its remaining values, applying the provided mapper function on each item. Remember that each invocation of `next` of an iterator consumes a value. `Iterator.toArrayWithMapper` will consume all remaining values of the iterator and return them in an array to you. -See [iterator protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) on MDN. +See [Iterator.prototype.toArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/toArray) on MDN. ## Examples ```rescript @@ -83,7 +84,7 @@ let mapKeysAsArray = map ->Map.keys ->Iterator.toArrayWithMapper(key => key->String.length) -Console.log(mapKeysAsArray) // Logs [7, 8] to the console. +mapKeysAsArray->assertEqual([7, 8]) ``` */ external toArrayWithMapper: (t<'a>, 'a => 'b) => array<'b> = "Array.from" @@ -95,25 +96,17 @@ See [iterator protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript ## Examples ```rescript -let iterator: Iterator.t = %raw(` - (() => { - var array1 = ['a', 'b', 'c']; - var iterator1 = array1[Symbol.iterator](); - return iterator1 - })() -`) +let iterator: Iterator.t = ["a", "b", "c"]->Array.values +let acc = ref("") iterator->Iterator.forEach(v => { - switch v { - | Some("a" | "b" | "c") => assert(true) - | other => - other - ->Option.isNone - ->assertEqual(true) - } + acc := acc.contents ++ v }) + +acc.contents->assertEqual("abc") ``` */ -let forEach: (t<'a>, option<'a> => unit) => unit +@send +external forEach: (t<'a>, 'a => unit) => unit = "forEach" /** `ignore(iterator)` ignores the provided iterator and returns unit. @@ -122,3 +115,157 @@ let forEach: (t<'a>, option<'a> => unit) => unit without having to store or process it further. */ external ignore: t<'a> => unit = "%ignore" + +/** +`drop((iterator, n))` returns a new iterator helper object that skips the given number of elements at the start of this iterator. + +See [Iterator.prototype.drop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/drop) on MDN. + +## Examples +```rescript +let fibonacci: Iterator.t = [ 1, 1, 2, 3, 5, 8, 13, 21 ]->Array.values + +let seq = fibonacci->Iterator.drop(2) +seq->Iterator.next->assertEqual({done: false, value: Some(2)}) +seq->Iterator.next->assertEqual({done: false, value: Some(3)}) +``` +*/ +@send +external drop: (t<'a>, int) => t<'a> = "drop" + +/** +`every(iterator, fn)` tests whether all elements in the iterator pass the test implemented by the provided function. + +See [Iterator.prototype.every](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/every) on MDN. + +## Examples +```rescript +let fibonacci: Iterator.t = [ 1, 1, 2, 3, 5, 8, 13, 21 ]->Array.values + +let areAllEven = fibonacci->Iterator.every(n => n % 2 == 0) +areAllEven->assertEqual(false) +``` +*/ +@send +external every: (t<'a>, 'a => bool) => bool = "every" + +/** +`filter(iterator, fn)` returns a new iterator helper object that contains the elements of the original iterator that pass the test implemented by the provided function. + +See [Iterator.prototype.filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/filter) on MDN. + +## Examples +```rescript +let fibonacci: Iterator.t = [ 1, 1, 2, 3, 5, 8, 13, 21 ]->Array.values + +let seq = fibonacci->Iterator.filter(n => n % 2 == 0) +seq->Iterator.next->assertEqual({done: false, value: Some(2)}) +seq->Iterator.next->assertEqual({done: false, value: Some(8)}) +``` +*/ +@send +external filter: (t<'a>, 'a => bool) => t<'a> = "filter" + +/** +`find(iterator, fn)` returns the value of the first element in the iterator that satisfies the provided testing function. + +See [Iterator.prototype.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/find) on MDN. + +## Examples +```rescript +let fibonacci: Iterator.t = [ 1, 1, 2, 3, 5, 8, 13, 21 ]->Array.values + +let seq = fibonacci->Iterator.find(n => n % 2 == 0) +seq->assertEqual(Some(2)) +``` +*/ +@send +external find: (t<'a>, 'a => bool) => option<'a> = "find" + +/** +`flatMap(iterator, fn)` returns a new iterator helper object that contains the elements of the original iterator that pass the test implemented by the provided function. + +See [Iterator.prototype.flatMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/flatMap) on MDN. + +## Examples +```rescript +let map1 = Map.fromArray([("a", 1), ("b", 2), ("c", 3)]) +let map2 = Map.fromArray([("d", 4), ("e", 5), ("f", 6)]) + +let letters = + [map1, map2] + ->Array.values + ->Iterator.flatMap(m => Map.keys(m)) + ->Array.fromIterator +letters->assertEqual(["a", "b", "c", "d", "e", "f"]) +``` + */ +@send +external flatMap: (t<'a>, 'a => t<'b>) => t<'b> = "flatMap" + +/** +`map(iterator, fn)` returns a new iterator helper object that yields elements of the iterator, each transformed by a mapping function. + +See [Iterator.prototype.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/map) on MDN. + +## Examples +```rescript +let map = Map.fromArray([("a", 1), ("b", 2), ("c", 3)]) +let letters = map->Map.keys->Iterator.map(v => v->String.toUpperCase)->Array.fromIterator +letters->assertEqual(["A", "B", "C"]) +``` +*/ +@send +external map: (t<'a>, 'a => 'b) => t<'b> = "map" + +/** +`reduce(iterator, fn, initialValue)` applies a function against an accumulator and each element in the iterator (from left to right) to reduce it to a single value. + +See [Iterator.prototype.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/reduce) on MDN. + +## Examples +```rescript +let numbers: Iterator.t = [ 1, 2, 3 ]->Array.values + +let sum = numbers->Iterator.reduce((acc, n) => acc + n, ~initialValue=0) +sum->assertEqual(6) +``` +*/ +@send +external reduce: (t<'a>, ('acc, 'a) => 'acc, ~initialValue: 'acc=?) => 'acc = "reduce" + +/** +`some(iterator, fn)` The some() method of Iterator instances is similar to Array.some: +it tests whether at least one element produced by the iterator passes the test implemented by the provided function. +It returns a boolean value. + +See [Iterator.prototype.some](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/some) on MDN. + +## Examples +```rescript +let numbers: Iterator.t = [ 1, 2, 3 ]->Array.values + +let hasEven = numbers->Iterator.some(n => n % 2 == 0) +hasEven->assertEqual(true) +``` + */ +@send +external some: (t<'a>, 'a => bool) => bool = "some" + +/** +`take((iterator, n))` returns a new iterator helper object that contains the first `n` elements of this iterator. + +See [Iterator.prototype.take](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/take) on MDN. + +## Examples +```rescript +let fibonacci: Iterator.t = [ 1, 1, 2, 3, 5, 8, 13, 21 ]->Array.values + +let seq = fibonacci->Iterator.take(2) +seq->Iterator.next->assertEqual({done: false, value: Some(1)}) +seq->Iterator.next->assertEqual({done: false, value: Some(1)}) +seq->Iterator.next->assertEqual({done: true, value: None}) +``` +*/ +@send +external take: (t<'a>, int) => t<'a> = "take" diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index f56854cd77..ca1a714121 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -328,6 +328,12 @@ Path Array. "tags": [], "detail": "(array<'a>, int) => 'a", "documentation": {"kind": "markdown", "value": "\n`getUnsafe(array, index)` returns the element at `index` of `array`.\n\nThis is _unsafe_, meaning it will return `undefined` value if `index` does not exist in `array`.\n\nUse `Array.getUnsafe` only when you are sure the `index` exists (i.e. when using for-loop).\n\n## Examples\n```rescript\nlet array = [1, 2, 3]\nfor index in 0 to array->Array.length - 1 {\n let value = array->Array.getUnsafe(index)\n Console.log(value)\n}\n```\n"} + }, { + "label": "entries", + "kind": 12, + "tags": [], + "detail": "array<'a> => Iterator.t<(int, 'a)>", + "documentation": {"kind": "markdown", "value": "\n`entries(array)` returns a new array iterator object that contains the key/value pairs for each index in the array.\n\nSee [Array.prototype.entries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries) on MDN.\n\n## Examples\n\n```rescript\nlet array = [5, 6, 7]\nlet iterator : Iterator.t<(int, int)> = array->Array.entries\niterator->Iterator.next->assertEqual({done: false, value: Some((0, 5))})\niterator->Iterator.next->assertEqual({done: false, value: Some((1, 6))})\n```\n"} }, { "label": "unshiftMany", "kind": 12, @@ -376,6 +382,12 @@ Path Array. "tags": [], "detail": "'a => bool", "documentation": null + }, { + "label": "values", + "kind": 12, + "tags": [], + "detail": "array<'a> => Iterator.t<'a>", + "documentation": {"kind": "markdown", "value": "\n`values(array)` returns a new array iterator object that contains the values for each index in the array.\n\nSee [Array.prototype.values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values) on MDN.\n\n## Examples\n\n```rescript\nlet array = [5, 6, 7]\nlet iterator : Iterator.t = array->Array.values\niterator->Iterator.next->assertEqual({done: false, value: Some(5)})\niterator->Iterator.next->assertEqual({done: false, value: Some(6)})\n```\n "} }, { "label": "indexOfOpt", "kind": 12, diff --git a/tests/tests/src/core/Core_IteratorTests.mjs b/tests/tests/src/core/Core_IteratorTests.mjs index c9a7e4868b..886cde45b4 100644 --- a/tests/tests/src/core/Core_IteratorTests.mjs +++ b/tests/tests/src/core/Core_IteratorTests.mjs @@ -1,7 +1,6 @@ // Generated by ReScript, PLEASE EDIT WITH CARE import * as Test from "./Test.mjs"; -import * as Stdlib_Iterator from "rescript/lib/es6/Stdlib_Iterator.js"; import * as Primitive_object from "rescript/lib/es6/Primitive_object.js"; import * as Stdlib_AsyncIterator from "rescript/lib/es6/Stdlib_AsyncIterator.js"; @@ -17,7 +16,7 @@ let syncResult = { contents: undefined }; -Stdlib_Iterator.forEach(iterator, v => { +iterator.forEach(v => { if (v === "b") { syncResult.contents = "b"; return; diff --git a/tests/tests/src/core/Core_IteratorTests.res b/tests/tests/src/core/Core_IteratorTests.res index 5c5d302297..8582a4c44a 100644 --- a/tests/tests/src/core/Core_IteratorTests.res +++ b/tests/tests/src/core/Core_IteratorTests.res @@ -11,7 +11,7 @@ let iterator: Iterator.t = %raw(` let syncResult = ref(None) iterator->Iterator.forEach(v => { - if v === Some("b") { + if v == "b" { syncResult.contents = Some("b") } }) diff --git a/tests/tests/src/core/Core_TempTests.mjs b/tests/tests/src/core/Core_TempTests.mjs index b2e214b011..993e9f63ee 100644 --- a/tests/tests/src/core/Core_TempTests.mjs +++ b/tests/tests/src/core/Core_TempTests.mjs @@ -249,7 +249,7 @@ let x = Symbol.for("Foo"); console.log(x); -let array$1 = Array.from("foo"[Symbol.iterator]()); +let array$1 = "foo"[Symbol.iterator]().toArray(); console.log(array$1);