From 764b4771106bc7ae83b39163843356226209f58c Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 30 Jul 2026 13:43:52 +0200 Subject: [PATCH 1/3] Align `List` membership APIs with `Array` --- packages/@rescript/runtime/Stdlib_List.res | 6 ++ packages/@rescript/runtime/Stdlib_List.resi | 23 +++++ .../@rescript/runtime/lib/es6/Stdlib_List.mjs | 15 +++ .../@rescript/runtime/lib/js/Stdlib_List.cjs | 15 +++ .../tests/src/expected/Completion.res.txt | 2 +- tests/tests/src/stdlib/Stdlib_ListTests.mjs | 91 +++++++++++++++++++ tests/tests/src/stdlib/Stdlib_ListTests.res | 18 ++++ tests/tests/src/stdlib/Stdlib_TestSuite.mjs | 1 + tests/tests/src/stdlib/Stdlib_TestSuite.res | 1 + 9 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 tests/tests/src/stdlib/Stdlib_ListTests.mjs create mode 100644 tests/tests/src/stdlib/Stdlib_ListTests.res diff --git a/packages/@rescript/runtime/Stdlib_List.res b/packages/@rescript/runtime/Stdlib_List.res index 3c8c3effacd..acfffe33185 100644 --- a/packages/@rescript/runtime/Stdlib_List.res +++ b/packages/@rescript/runtime/Stdlib_List.res @@ -679,6 +679,12 @@ let rec has = (xs, x, eq) => | list{a, ...l} => eq(a, x) || has(l, x, eq) } +let rec includes = (xs, x) => + switch xs { + | list{} => false + | list{a, ...l} => a === x || includes(l, x) + } + @deprecated("Use a `Map` instead") let rec getAssoc = (xs, x, eq) => switch xs { diff --git a/packages/@rescript/runtime/Stdlib_List.resi b/packages/@rescript/runtime/Stdlib_List.resi index bd150d8269b..8d9e81554e5 100644 --- a/packages/@rescript/runtime/Stdlib_List.resi +++ b/packages/@rescript/runtime/Stdlib_List.resi @@ -759,6 +759,8 @@ let equal: (list<'a>, list<'a>, ('a, 'a) => bool) => bool `has(list, element, f)` returns `true` if the list contains at least one `element` for which `f` returns \`true'. +Use [`some`](#some) with a predicate instead. + ## Examples ```rescript @@ -769,8 +771,29 @@ list{1, 2, 3}->List.has(4, (a, b) => a == b) == false list{-1, -2, -3}->List.has(2, (a, b) => abs(a) == abs(b)) == true ``` */ +@deprecated("Use `some` instead") let has: (list<'a>, 'b, ('a, 'b) => bool) => bool +/** +`includes(list, item)` checks whether `list` includes `item` using strict equality. + +Object values are only equal when they refer to the same object. `NaN` is not +equal to itself. + +## Examples + +```rescript +list{1, 2}->List.includes(1) == true +list{1, 2}->List.includes(3) == false +list{Float.Constants.nan}->List.includes(Float.Constants.nan) == false + +let item = {"language": "ReScript"} +list{item}->List.includes(item) == true +list{item}->List.includes({"language": "ReScript"}) == false +``` +*/ +let includes: (list<'a>, 'a) => bool + /** `find(list, f)` returns `Some(value)` for the first value in `list` that satisfies the predicate function `f`. Returns `None` if no element satisfies diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_List.mjs b/packages/@rescript/runtime/lib/es6/Stdlib_List.mjs index 1d959608b87..9eba9d6fb82 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib_List.mjs +++ b/packages/@rescript/runtime/lib/es6/Stdlib_List.mjs @@ -1043,6 +1043,20 @@ function has(_xs, x, eq) { }; } +function includes(_xs, x) { + while (true) { + let xs = _xs; + if (xs === 0) { + return false; + } + if (xs.hd === x) { + return true; + } + _xs = xs.tl; + continue; + }; +} + function getAssoc(_xs, x, eq) { while (true) { let xs = _xs; @@ -1351,6 +1365,7 @@ export { compare, equal, has, + includes, find, filter, filterWithIndex, diff --git a/packages/@rescript/runtime/lib/js/Stdlib_List.cjs b/packages/@rescript/runtime/lib/js/Stdlib_List.cjs index 3832df34626..ad5827e92e9 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib_List.cjs +++ b/packages/@rescript/runtime/lib/js/Stdlib_List.cjs @@ -1043,6 +1043,20 @@ function has(_xs, x, eq) { }; } +function includes(_xs, x) { + while (true) { + let xs = _xs; + if (xs === 0) { + return false; + } + if (xs.hd === x) { + return true; + } + _xs = xs.tl; + continue; + }; +} + function getAssoc(_xs, x, eq) { while (true) { let xs = _xs; @@ -1350,6 +1364,7 @@ exports.compareLength = compareLength; exports.compare = compare; exports.equal = equal; exports.has = has; +exports.includes = includes; exports.find = find; exports.filter = filter; exports.filterWithIndex = filterWithIndex; diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index f679ec14ced..6e6cd3897bd 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -315,7 +315,7 @@ Path Array. "detail": "(array<'a>, 'a) => bool", "documentation": { "kind": "markdown", - "value": "\n`includes(array, item)` checks whether `array` includes `item`, by doing a [strict check for equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality).\n\nSee [`Array.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) on MDN.\n\n## Examples\n\n```rescript\n[1, 2]->Array.includes(1) == true\n[1, 2]->Array.includes(3) == false\n\n[{\"language\": \"ReScript\"}]->Array.includes({\"language\": \"ReScript\"}) == false // false, because of strict equality\n```\n" + "value": "\n`includes(array, item)` checks whether `array` includes `item` using\n[SameValueZero equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Equality_comparisons_and_sameness#same-value-zero_equality).\n\nSee [`Array.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) on MDN.\n\n## Examples\n\n```rescript\n[1, 2]->Array.includes(1) == true\n[1, 2]->Array.includes(3) == false\n[Float.Constants.nan]->Array.includes(Float.Constants.nan) == true\n\n[{\"language\": \"ReScript\"}]->Array.includes({\"language\": \"ReScript\"}) == false // false, because objects compare by reference\n```\n" }, "kind": 12, "label": "includes", diff --git a/tests/tests/src/stdlib/Stdlib_ListTests.mjs b/tests/tests/src/stdlib/Stdlib_ListTests.mjs new file mode 100644 index 00000000000..ade82ae9847 --- /dev/null +++ b/tests/tests/src/stdlib/Stdlib_ListTests.mjs @@ -0,0 +1,91 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Test from "./Test.mjs"; +import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.mjs"; +import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.mjs"; + +let eq = Primitive_object.equal; + +Test.run([ + [ + "Stdlib_ListTests.res", + 3, + 20, + 40 + ], + "includes - present" +], Stdlib_List.includes({ + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } +}, 2), eq, true); + +Test.run([ + [ + "Stdlib_ListTests.res", + 4, + 20, + 40 + ], + "includes - missing" +], Stdlib_List.includes({ + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } +}, 4), eq, false); + +Test.run([ + [ + "Stdlib_ListTests.res", + 5, + 20, + 38 + ], + "includes - empty" +], Stdlib_List.includes(/* [] */0, 1), eq, false); + +let item = { + language: "ReScript" +}; + +let items = { + hd: item, + tl: /* [] */0 +}; + +Test.run([ + [ + "Stdlib_ListTests.res", + 11, + 22, + 46 + ], + "includes - same object" +], Stdlib_List.includes(items, item), eq, true); + +Test.run([ + [ + "Stdlib_ListTests.res", + 13, + 15, + 53 + ], + "includes - structurally equal object" +], Stdlib_List.includes(items, { + language: "ReScript" +}), eq, false); + +export { + eq, +} +/* Not a pure module */ diff --git a/tests/tests/src/stdlib/Stdlib_ListTests.res b/tests/tests/src/stdlib/Stdlib_ListTests.res new file mode 100644 index 00000000000..4cf6da76750 --- /dev/null +++ b/tests/tests/src/stdlib/Stdlib_ListTests.res @@ -0,0 +1,18 @@ +let eq = (a, b) => a == b + +Test.run(__POS_OF__("includes - present"), list{1, 2, 3}->List.includes(2), eq, true) +Test.run(__POS_OF__("includes - missing"), list{1, 2, 3}->List.includes(4), eq, false) +Test.run(__POS_OF__("includes - empty"), list{}->List.includes(1), eq, false) + +{ + let item = {"language": "ReScript"} + let items = list{item} + + Test.run(__POS_OF__("includes - same object"), items->List.includes(item), eq, true) + Test.run( + __POS_OF__("includes - structurally equal object"), + items->List.includes({"language": "ReScript"}), + eq, + false, + ) +} diff --git a/tests/tests/src/stdlib/Stdlib_TestSuite.mjs b/tests/tests/src/stdlib/Stdlib_TestSuite.mjs index 77fd06e5a0d..d5432490868 100644 --- a/tests/tests/src/stdlib/Stdlib_TestSuite.mjs +++ b/tests/tests/src/stdlib/Stdlib_TestSuite.mjs @@ -3,6 +3,7 @@ import * as Stdlib_IntTests from "./Stdlib_IntTests.mjs"; import * as Stdlib_DictTests from "./Stdlib_DictTests.mjs"; import * as Stdlib_JsonTests from "./Stdlib_JsonTests.mjs"; +import * as Stdlib_ListTests from "./Stdlib_ListTests.mjs"; import * as Stdlib_TestTests from "./Stdlib_TestTests.mjs"; import * as Stdlib_ArrayTests from "./Stdlib_ArrayTests.mjs"; import * as Stdlib_ErrorTests from "./Stdlib_ErrorTests.mjs"; diff --git a/tests/tests/src/stdlib/Stdlib_TestSuite.res b/tests/tests/src/stdlib/Stdlib_TestSuite.res index 47fc18e3ecf..6ea9e4ed2df 100644 --- a/tests/tests/src/stdlib/Stdlib_TestSuite.res +++ b/tests/tests/src/stdlib/Stdlib_TestSuite.res @@ -2,6 +2,7 @@ include Stdlib_TestTests include Stdlib_PromiseTest include Stdlib_ErrorTests include Stdlib_ArrayTests +include Stdlib_ListTests include Stdlib_IntTests include Stdlib_ObjectTests include Stdlib_ResultTests From bbefde671814a860f01ba0a286425fdb2f609de9 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 7 Aug 2026 15:42:42 +0200 Subject: [PATCH 2/3] Fix documentation for `Array.includes` --- packages/@rescript/runtime/Stdlib_Array.resi | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@rescript/runtime/Stdlib_Array.resi b/packages/@rescript/runtime/Stdlib_Array.resi index 45ca887b6f3..0176d94c3eb 100644 --- a/packages/@rescript/runtime/Stdlib_Array.resi +++ b/packages/@rescript/runtime/Stdlib_Array.resi @@ -609,7 +609,8 @@ See [`Array.flat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer external flat: array> => array<'a> = "flat" /** -`includes(array, item)` checks whether `array` includes `item`, by doing a [strict check for equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality). +`includes(array, item)` checks whether `array` includes `item` using +[SameValueZero equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Equality_comparisons_and_sameness#same-value-zero_equality). See [`Array.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) on MDN. @@ -618,8 +619,9 @@ See [`Array.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/R ```rescript [1, 2]->Array.includes(1) == true [1, 2]->Array.includes(3) == false +[Float.Constants.nan]->Array.includes(Float.Constants.nan) == true -[{"language": "ReScript"}]->Array.includes({"language": "ReScript"}) == false // false, because of strict equality +[{"language": "ReScript"}]->Array.includes({"language": "ReScript"}) == false // false, because objects compare by reference ``` */ @send From fd23b01fdec82b18e756c343cff833446896379f Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 7 Aug 2026 16:16:50 +0200 Subject: [PATCH 3/3] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e9abbbe6c..c8c9188761f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ #### :rocket: New Feature - Add source map support with linked, inline, and hidden modes. https://github.com/rescript-lang/rescript/pull/8393 +- Add `List.includes`, deprecate `List.has` in favor of `List.some`, and clarify the equality semantics of `List.includes` and `Array.includes`. https://github.com/rescript-lang/rescript/pull/8530 #### :bug: Bug fix