array-callbacks-test262.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /*
  2. * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
  3. * - test/built-ins/Array/prototype/map/15.4.4.19-8-1.js
  4. * - test/built-ins/Array/prototype/map/15.4.4.19-8-2.js
  5. * - test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js
  6. * - test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js
  7. * - test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js
  8. * - test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js
  9. * - test/built-ins/Array/prototype/find/predicate-call-parameters.js
  10. * - test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js
  11. * - test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js
  12. * - test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js
  13. * - test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js
  14. * - test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js
  15. * - test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js
  16. * - test/built-ins/Array/prototype/findLast/predicate-call-parameters.js
  17. * - test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js
  18. * - test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js
  19. * - test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js
  20. * - test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js
  21. * - test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js
  22. * - test/built-ins/Array/prototype/some/15.4.4.17-7-1.js
  23. * - test/built-ins/Array/prototype/some/15.4.4.17-8-1.js
  24. * - test/built-ins/Array/prototype/every/15.4.4.16-7-1.js
  25. * - test/built-ins/Array/prototype/every/15.4.4.16-8-1.js
  26. * - test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js
  27. * - test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js
  28. * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js
  29. * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-c-ii-20.js
  30. * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js
  31. * - test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js
  32. * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js
  33. * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-c-ii-20.js
  34. * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js
  35. * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js
  36. * - test/built-ins/Array/prototype/flatMap/depth-always-one.js
  37. * - test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js
  38. * - test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js
  39. * - test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js
  40. * - test/built-ins/Array/prototype/sort/stability-5-elements.js
  41. * - test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js
  42. * - test/built-ins/Array/prototype/toSorted/comparefn-default.js
  43. * - test/built-ins/Array/prototype/toSorted/immutable.js
  44. * - test/built-ins/Array/prototype/toSorted/zero-or-one-element.js
  45. *
  46. * Copyright (C) 2015 the V8 project authors. All rights reserved.
  47. * Copyright (C) 2018 Mathias Bynens. All rights reserved.
  48. * Copyright (C) 2018 Shilpi Jain and Michael Ficarra. All rights reserved.
  49. * Copyright (C) 2021 Igalia, S.L. All rights reserved.
  50. * Copyright (C) 2021 Microsoft. All rights reserved.
  51. * Copyright (C) 2025 Google. All rights reserved.
  52. * Copyright (C) 2026 Garham Lee. All rights reserved.
  53. * Copyright (c) 2012 Ecma International. All rights reserved.
  54. * Copyright 2009 the Sputnik authors. All rights reserved.
  55. * Test262 portions are governed by the BSD license in LICENSE.test262.
  56. */
  57. import { describe, expect, test } from "bun:test"
  58. import { Effect } from "effect"
  59. import { CodeMode } from "../src/index.js"
  60. const value = async (code: string) => {
  61. const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  62. if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  63. return result.value
  64. }
  65. const cases = [
  66. {
  67. path: "test/built-ins/Array/prototype/map/15.4.4.19-8-1.js",
  68. code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.map((value) => { input[2] = 3; input[5] = 6; return 1 }); return [result.length, result[5] === undefined]`,
  69. expected: [5, true],
  70. },
  71. {
  72. path: "test/built-ins/Array/prototype/map/15.4.4.19-8-2.js",
  73. code: `const input = [1, 2, 3, 4, 5]; const result = input.map((value) => { input[4] = -1; return value > 0 ? 1 : 0 }); return [result.length, result[4]]`,
  74. expected: [5, 0],
  75. },
  76. {
  77. path: "test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js",
  78. code: `const input = []; input[10] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.map(() => { calls += 1; return 1 }); return [result.length, calls, 0 in result, 1 in result]`,
  79. expected: [10, 1, false, true],
  80. },
  81. {
  82. path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js",
  83. code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.filter(() => { input[2] = 3; input[5] = 6; return true }); return result`,
  84. expected: [1, 2, 3, 4, 5],
  85. },
  86. {
  87. path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js",
  88. code: `const input = [1, 2, 3, 4, 5]; return input.filter((value) => { input[2] = -1; input[4] = -1; return value > 0 })`,
  89. expected: [1, 2, 4],
  90. },
  91. {
  92. path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js",
  93. code: `const input = []; input[9] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.filter(() => { calls += 1; return false }); return [result, calls]`,
  94. expected: [[], 1],
  95. },
  96. {
  97. path: "test/built-ins/Array/prototype/find/predicate-call-parameters.js",
  98. code: `const input = [10, 20]; const seen = []; input.find((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
  99. expected: [
  100. [10, 0, true],
  101. [20, 1, true],
  102. ],
  103. },
  104. {
  105. path: "test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js",
  106. code: `return [1, 2, 3].find((value) => value > 1)`,
  107. expected: 2,
  108. },
  109. {
  110. path: "test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js",
  111. code: `return [1, 2, 3].find((value) => value > 4) === undefined`,
  112. expected: true,
  113. },
  114. {
  115. path: "test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js",
  116. code: `let calls = 0; const result = [].find(() => { calls += 1; return true }); return [result === undefined, calls]`,
  117. expected: [true, 0],
  118. },
  119. {
  120. path: "test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js",
  121. code: `const input = [10, 20]; const seen = []; input.findIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
  122. expected: [
  123. [10, 0, true],
  124. [20, 1, true],
  125. ],
  126. },
  127. {
  128. path: "test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js",
  129. code: `return [1, 2, 3].findIndex((value) => value > 1)`,
  130. expected: 1,
  131. },
  132. {
  133. path: "test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js",
  134. code: `return [1, 2, 3].findIndex((value) => value > 4)`,
  135. expected: -1,
  136. },
  137. {
  138. path: "test/built-ins/Array/prototype/findLast/predicate-call-parameters.js",
  139. code: `const input = [10, 20]; const seen = []; input.findLast((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
  140. expected: [
  141. [20, 1, true],
  142. [10, 0, true],
  143. ],
  144. },
  145. {
  146. path: "test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js",
  147. code: `return [1, 2, 3].findLast((value) => value < 3)`,
  148. expected: 2,
  149. },
  150. {
  151. path: "test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js",
  152. code: `return [1, 2, 3].findLast((value) => value > 4) === undefined`,
  153. expected: true,
  154. },
  155. {
  156. path: "test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js",
  157. code: `const input = [10, 20]; const seen = []; input.findLastIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
  158. expected: [
  159. [20, 1, true],
  160. [10, 0, true],
  161. ],
  162. },
  163. {
  164. path: "test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js",
  165. code: `return [1, 2, 3].findLastIndex((value) => value < 3)`,
  166. expected: 1,
  167. },
  168. {
  169. path: "test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js",
  170. code: `return [1, 2, 3].findLastIndex((value) => value > 4)`,
  171. expected: -1,
  172. },
  173. {
  174. path: "test/built-ins/Array/prototype/some/15.4.4.17-7-1.js",
  175. code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.some((value) => { input[2] = 3; seen.push(value); return false }); return [result, seen.includes(3)]`,
  176. expected: [false, true],
  177. },
  178. {
  179. path: "test/built-ins/Array/prototype/some/15.4.4.17-8-1.js",
  180. code: `return [].some(() => true)`,
  181. expected: false,
  182. },
  183. {
  184. path: "test/built-ins/Array/prototype/every/15.4.4.16-7-1.js",
  185. code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.every((value) => { input[2] = 3; seen.push(value); return true }); return [result, seen.includes(3)]`,
  186. expected: [true, true],
  187. },
  188. {
  189. path: "test/built-ins/Array/prototype/every/15.4.4.16-8-1.js",
  190. code: `return [].every(() => false)`,
  191. expected: true,
  192. },
  193. {
  194. path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js",
  195. code: `const input = [1, 2]; input[3] = 4; input[4] = 5; let calls = 0; input.forEach(() => { calls += 1; input[2] = 3; input[5] = 6 }); return calls`,
  196. expected: 5,
  197. },
  198. {
  199. path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js",
  200. code: `const input = [1, 2, 3]; const seen = []; input.forEach((value, index) => { seen.push(value); if (index === 0) input.pop() }); return seen`,
  201. expected: [1, 2],
  202. },
  203. {
  204. path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js",
  205. code: `const input = [1, 2]; input[3] = 4; input[4] = "5"; return input.reduce((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`,
  206. expected: "105",
  207. },
  208. {
  209. path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js",
  210. code: `let calls = 0; const result = [1].reduce(() => { calls += 1; return 2 }); return [result, calls]`,
  211. expected: [1, 0],
  212. },
  213. {
  214. path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-c-ii-20.js",
  215. code: `let accessed = false; const result = [11].reduce((previous) => { accessed = true; return previous === undefined }, undefined); return [result, accessed]`,
  216. expected: [true, true],
  217. },
  218. {
  219. path: "test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js",
  220. code: `const input = [1, 2, 3, 4, 5]; input.reduce(() => 1); return input`,
  221. expected: [1, 2, 3, 4, 5],
  222. },
  223. {
  224. path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js",
  225. code: `const input = ["1", 2]; input[3] = 4; input[4] = "5"; return input.reduceRight((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`,
  226. expected: "54321",
  227. },
  228. {
  229. path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js",
  230. code: `let calls = 0; const result = [1].reduceRight(() => { calls += 1; return 2 }); return [result, calls]`,
  231. expected: [1, 0],
  232. },
  233. {
  234. path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-c-ii-20.js",
  235. code: `let accessed = false; const result = [11].reduceRight((previous) => { accessed = true; return previous === undefined }, undefined); return [result, accessed]`,
  236. expected: [true, true],
  237. },
  238. {
  239. path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js",
  240. code: `const input = [1, 2, 3, 4, 5]; input.reduceRight(() => 1); return input`,
  241. expected: [1, 2, 3, 4, 5],
  242. },
  243. {
  244. path: "test/built-ins/Array/prototype/flatMap/depth-always-one.js",
  245. code: `return [1, 2, 3].flatMap((value) => [[value * 2]])`,
  246. expected: [[2], [4], [6]],
  247. },
  248. {
  249. path: "test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js",
  250. code: `try { [1, 2].flatMap(() => { throw "stop" }) } catch (error) { return error === "stop" } return false`,
  251. expected: true,
  252. },
  253. {
  254. path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js",
  255. code: `const input = []; input[2] = 0; input.pop(); input.sort(); return [input.length, input[0] === undefined, input[1] === undefined]`,
  256. expected: [2, true, true],
  257. },
  258. {
  259. path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js",
  260. code: `return ["z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n", "M", "L", "K", "J", "I", "H", "G", "F", "E", "D", "C", "B", "A"].sort()`,
  261. expected: [
  262. "A",
  263. "B",
  264. "C",
  265. "D",
  266. "E",
  267. "F",
  268. "G",
  269. "H",
  270. "I",
  271. "J",
  272. "K",
  273. "L",
  274. "M",
  275. "n",
  276. "o",
  277. "p",
  278. "q",
  279. "r",
  280. "s",
  281. "t",
  282. "u",
  283. "v",
  284. "w",
  285. "x",
  286. "y",
  287. "z",
  288. ],
  289. },
  290. {
  291. path: "test/built-ins/Array/prototype/sort/stability-5-elements.js",
  292. code: `const input = [{ n: "A", r: 2 }, { n: "B", r: 3 }, { n: "C", r: 2 }, { n: "D", r: 3 }, { n: "E", r: 3 }]; return input.sort((left, right) => right.r - left.r).map((item) => item.n).join("")`,
  293. expected: "BDEAC",
  294. },
  295. {
  296. path: "test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js",
  297. code: `const mixed = [333, 33, 3, 222, 22, 2, 111, 11, 1]; return [[1, 2, 3, 4].toSorted((a, b) => a - b), [4, 3, 2, 1].toSorted((a, b) => a - b), mixed.toSorted((a, b) => a - b), [1, 2, 3, 4].toSorted((a, b) => b - a), [4, 3, 2, 1].toSorted((a, b) => b - a), mixed.toSorted((a, b) => b - a)]`,
  298. expected: [
  299. [1, 2, 3, 4],
  300. [1, 2, 3, 4],
  301. [1, 2, 3, 11, 22, 33, 111, 222, 333],
  302. [4, 3, 2, 1],
  303. [4, 3, 2, 1],
  304. [333, 222, 111, 33, 22, 11, 3, 2, 1],
  305. ],
  306. },
  307. {
  308. path: "test/built-ins/Array/prototype/toSorted/comparefn-default.js",
  309. code: `return [[1, 2, 3, 4].toSorted(), [4, 3, 2, 1].toSorted(), ["a", 2, 1, "z"].toSorted(), [333, 33, 3, 222, 22, 2, 111, 11, 1].toSorted()]`,
  310. expected: [
  311. [1, 2, 3, 4],
  312. [1, 2, 3, 4],
  313. [1, 2, "a", "z"],
  314. [1, 11, 111, 2, 22, 222, 3, 33, 333],
  315. ],
  316. },
  317. {
  318. path: "test/built-ins/Array/prototype/toSorted/immutable.js",
  319. code: `const input = [2, 0, 1]; const result = input.toSorted(); return [input, result !== input]`,
  320. expected: [[2, 0, 1], true],
  321. },
  322. {
  323. path: "test/built-ins/Array/prototype/toSorted/zero-or-one-element.js",
  324. code: `const zero = []; const one = [1]; const zeroResult = zero.toSorted(); const oneResult = one.toSorted(); return [zeroResult, oneResult, zeroResult !== zero, oneResult !== one]`,
  325. expected: [[], [1], true, true],
  326. },
  327. ] as const
  328. describe("Test262 Array callback adaptations", () => {
  329. for (const item of cases) {
  330. test(item.path, async () => {
  331. expect(await value(item.code)).toEqual(item.expected)
  332. })
  333. }
  334. })
  335. describe("Array callback regressions", () => {
  336. test("reduce and reduceRight find the first present element", async () => {
  337. expect(
  338. await value(`
  339. const left = []
  340. left[2] = 3
  341. const right = []
  342. right[0] = 4
  343. right[3] = 1
  344. right.pop()
  345. return [left.reduce((a, b) => a + b), right.reduceRight((a, b) => a + b)]
  346. `),
  347. ).toEqual([3, 4])
  348. })
  349. test("reduce and reduceRight reject arrays containing only holes", async () => {
  350. expect(
  351. await value(`
  352. const values = []
  353. values[2] = 1
  354. values.pop()
  355. let left
  356. let right
  357. try { values.reduce((a, b) => a + b) } catch (error) { left = error.name }
  358. try { values.reduceRight((a, b) => a + b) } catch (error) { right = error.name }
  359. return [left, right]
  360. `),
  361. ).toEqual(["TypeError", "TypeError"])
  362. })
  363. test("findLast returns the value observed before predicate mutation", async () => {
  364. expect(
  365. await value(`
  366. const values = [1]
  367. return values.findLast((item, index, array) => {
  368. array[index] = 2
  369. return true
  370. })
  371. `),
  372. ).toBe(1)
  373. })
  374. })