set-methods-test262.test.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
  3. * - test/built-ins/Set/prototype/union/combines-sets.js
  4. * - test/built-ins/Set/prototype/union/combines-empty-sets.js
  5. * - test/built-ins/Set/prototype/union/combines-itself.js
  6. * - test/built-ins/Set/prototype/union/combines-same-sets.js
  7. * - test/built-ins/Set/prototype/union/combines-Map.js
  8. * - test/built-ins/Set/prototype/union/result-order.js
  9. * - test/built-ins/Set/prototype/union/appends-new-values.js
  10. * - test/built-ins/Set/prototype/union/allows-set-like-object.js
  11. * - test/built-ins/Set/prototype/union/size-is-a-number.js
  12. * - test/built-ins/Set/prototype/union/has-is-callable.js
  13. * - test/built-ins/Set/prototype/union/keys-is-callable.js
  14. * - test/built-ins/Set/prototype/union/array-throws.js
  15. * - test/built-ins/Set/prototype/union/converts-negative-zero.js
  16. * - test/built-ins/Set/prototype/union/set-like-class-mutation.js
  17. * - test/built-ins/Set/prototype/intersection/combines-sets.js
  18. * - test/built-ins/Set/prototype/intersection/combines-itself.js
  19. * - test/built-ins/Set/prototype/intersection/result-order.js
  20. * - test/built-ins/Set/prototype/intersection/size-is-a-number.js
  21. * - test/built-ins/Set/prototype/intersection/converts-negative-zero.js
  22. * - test/built-ins/Set/prototype/intersection/combines-Map.js
  23. * - test/built-ins/Set/prototype/intersection/allows-set-like-object.js
  24. * - test/built-ins/Set/prototype/intersection/set-like-class-mutation.js
  25. * - test/built-ins/Set/prototype/difference/combines-sets.js
  26. * - test/built-ins/Set/prototype/difference/combines-itself.js
  27. * - test/built-ins/Set/prototype/difference/result-order.js
  28. * - test/built-ins/Set/prototype/difference/size-is-a-number.js
  29. * - test/built-ins/Set/prototype/difference/converts-negative-zero.js
  30. * - test/built-ins/Set/prototype/difference/combines-Map.js
  31. * - test/built-ins/Set/prototype/difference/allows-set-like-object.js
  32. * - test/built-ins/Set/prototype/difference/set-like-class-mutation.js
  33. * - test/built-ins/Set/prototype/symmetricDifference/combines-sets.js
  34. * - test/built-ins/Set/prototype/symmetricDifference/combines-itself.js
  35. * - test/built-ins/Set/prototype/symmetricDifference/result-order.js
  36. * - test/built-ins/Set/prototype/symmetricDifference/size-is-a-number.js
  37. * - test/built-ins/Set/prototype/symmetricDifference/converts-negative-zero.js
  38. * - test/built-ins/Set/prototype/symmetricDifference/set-like-class-mutation.js
  39. * - test/built-ins/Set/prototype/symmetricDifference/combines-Map.js
  40. * - test/built-ins/Set/prototype/symmetricDifference/allows-set-like-object.js
  41. * - test/built-ins/Set/prototype/isSubsetOf/compares-sets.js
  42. * - test/built-ins/Set/prototype/isSubsetOf/size-is-a-number.js
  43. * - test/built-ins/Set/prototype/isSubsetOf/compares-Map.js
  44. * - test/built-ins/Set/prototype/isSubsetOf/allows-set-like-object.js
  45. * - test/built-ins/Set/prototype/isSubsetOf/compares-itself.js
  46. * - test/built-ins/Set/prototype/isSubsetOf/compares-same-sets.js
  47. * - test/built-ins/Set/prototype/isSubsetOf/set-like-class-mutation.js
  48. * - test/built-ins/Set/prototype/isSupersetOf/compares-sets.js
  49. * - test/built-ins/Set/prototype/isSupersetOf/size-is-a-number.js
  50. * - test/built-ins/Set/prototype/isSupersetOf/compares-Map.js
  51. * - test/built-ins/Set/prototype/isSupersetOf/allows-set-like-object.js
  52. * - test/built-ins/Set/prototype/isSupersetOf/compares-itself.js
  53. * - test/built-ins/Set/prototype/isSupersetOf/compares-same-sets.js
  54. * - test/built-ins/Set/prototype/isSupersetOf/converts-negative-zero.js
  55. * - test/built-ins/Set/prototype/isDisjointFrom/compares-sets.js
  56. * - test/built-ins/Set/prototype/isDisjointFrom/size-is-a-number.js
  57. * - test/built-ins/Set/prototype/isDisjointFrom/compares-Map.js
  58. * - test/built-ins/Set/prototype/isDisjointFrom/allows-set-like-object.js
  59. * - test/built-ins/Set/prototype/isDisjointFrom/compares-itself.js
  60. * - test/built-ins/Set/prototype/isDisjointFrom/compares-same-sets.js
  61. * - test/built-ins/Set/prototype/isDisjointFrom/converts-negative-zero.js
  62. * - test/built-ins/Set/prototype/isDisjointFrom/set-like-class-mutation.js
  63. *
  64. * Copyright (C) 2023 Anthony Frehner. All rights reserved.
  65. * Copyright (C) 2023 Anthony Frehner and Kevin Gibbons. All rights reserved.
  66. * Copyright (C) 2023 Kevin Gibbons. All rights reserved.
  67. * Copyright (C) 2023 Kevin Gibbons, Anthony Frehner. All rights reserved.
  68. * Test262 portions are governed by the BSD license in LICENSE.test262.
  69. * Set-like `keys` methods return arrays instead of iterator objects because
  70. * CodeMode materializes supported iterators and does not support generators.
  71. */
  72. import { describe, expect, test } from "bun:test"
  73. import { Effect } from "effect"
  74. import { CodeMode } from "../src/index.js"
  75. const value = async (code: string) => {
  76. const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  77. if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  78. return result.value
  79. }
  80. describe("Set composition Test262 parity", () => {
  81. test("combines Sets without mutating either operand", async () => {
  82. expect(
  83. await value(`
  84. const left = new Set([1, 2])
  85. const right = new Set([2, 3])
  86. const union = left.union(right)
  87. const intersection = left.intersection(right)
  88. const difference = left.difference(right)
  89. const symmetric = left.symmetricDifference(right)
  90. return [
  91. [...union], union instanceof Set,
  92. [...intersection], intersection instanceof Set,
  93. [...difference], difference instanceof Set,
  94. [...symmetric], symmetric instanceof Set,
  95. [...left], [...right],
  96. [...left.union(left)], left.union(left) !== left,
  97. [...left.intersection(left)], left.intersection(left) !== left,
  98. [...left.difference(left)], left.difference(left) !== left,
  99. [...left.symmetricDifference(left)], left.symmetricDifference(left) !== left,
  100. [...left.union(new Set())], [...new Set().union(left)],
  101. ]
  102. `),
  103. ).toEqual([
  104. [1, 2, 3],
  105. true,
  106. [2],
  107. true,
  108. [1],
  109. true,
  110. [1, 3],
  111. true,
  112. [1, 2],
  113. [2, 3],
  114. [1, 2],
  115. true,
  116. [1, 2],
  117. true,
  118. [],
  119. true,
  120. [],
  121. true,
  122. [1, 2],
  123. [1, 2],
  124. ])
  125. })
  126. test("preserves the specified result order", async () => {
  127. expect(
  128. await value(`
  129. return [
  130. [...new Set([1, 3, 5]).intersection(new Set([3, 2, 1]))],
  131. [...new Set([3, 2, 1, 0]).intersection(new Set([1, 3, 5]))],
  132. [...new Set([1, 2, 3, 4]).difference(new Set([6, 5, 3, 2]))],
  133. [...new Set([6, 5, 3, 2]).difference(new Set([1, 2, 3, 4]))],
  134. [...new Set([1, 2, 3, 4]).symmetricDifference(new Set([6, 5, 4, 3]))],
  135. [...new Set([2, 3]).union(new Set([1, 2]))],
  136. [...new Set([1, 2, -3]).union(new Set([-1, 0]))],
  137. ]
  138. `),
  139. ).toEqual([
  140. [1, 3],
  141. [1, 3],
  142. [1, 4],
  143. [6, 5],
  144. [1, 2, 6, 5],
  145. [2, 3, 1],
  146. [1, 2, -3, -1, 0],
  147. ])
  148. })
  149. test("accepts Maps as Set-like operands", async () => {
  150. expect(
  151. await value(`
  152. const set = new Set([1, 2])
  153. const map = new Map([[2, "two"], [3, "three"]])
  154. return [
  155. [...set.union(map)],
  156. [...set.intersection(map)],
  157. [...set.difference(map)],
  158. [...set.symmetricDifference(map)],
  159. ]
  160. `),
  161. ).toEqual([[1, 2, 3], [2], [1], [1, 3]])
  162. })
  163. test("accepts supported Set-like objects and uses the size-selected method", async () => {
  164. expect(
  165. await value(`
  166. const keysOnly = {
  167. size: 1,
  168. has: () => { throw new Error("has should not be called") },
  169. keys: () => [2, 3],
  170. }
  171. const hasOnly = {
  172. size: 2,
  173. has: (value) => value === 2,
  174. keys: () => { throw new Error("keys should not be called") },
  175. }
  176. const set = new Set([1, 2])
  177. return [
  178. [...set.union(keysOnly)],
  179. [...set.intersection(hasOnly)],
  180. [...set.difference(hasOnly)],
  181. [...set.symmetricDifference(keysOnly)],
  182. ]
  183. `),
  184. ).toEqual([[1, 2, 3], [2], [1], [1, 3]])
  185. })
  186. test("normalizes negative zero", async () => {
  187. expect(
  188. await value(`
  189. const setlike = {
  190. size: 1,
  191. has: () => { throw new Error("has should not be called") },
  192. keys: () => [-0],
  193. }
  194. return [
  195. 1 / new Set([1]).union(setlike).values()[1] === Infinity,
  196. 1 / new Set([0, 1, 2]).intersection(setlike).values()[0] === Infinity,
  197. [...new Set([0, 1]).difference(setlike)],
  198. 1 / new Set([1, 2]).symmetricDifference(setlike).values()[2] === Infinity,
  199. ]
  200. `),
  201. ).toEqual([true, true, [1], true])
  202. })
  203. test("handles duplicate Set-like keys using receiver membership", async () => {
  204. expect(
  205. await value(`
  206. const other = {
  207. size: 4,
  208. has: () => { throw new Error("has should not be called") },
  209. keys: () => [2, 2, 3, 3],
  210. }
  211. return [...new Set([1, 2]).symmetricDifference(other)]
  212. `),
  213. ).toEqual([1, 3])
  214. })
  215. test("copies receiver data before Set-like key iteration mutates it", async () => {
  216. expect(
  217. await value(`
  218. const unionBase = new Set(["a", "b", "c", "d", "e"])
  219. const unionOther = {
  220. size: 2,
  221. has: () => { throw new Error("has should not be called") },
  222. keys: () => {
  223. unionBase.delete("b")
  224. unionBase.delete("c")
  225. unionBase.add("b")
  226. return ["x", "y"]
  227. },
  228. }
  229. const union = unionBase.union(unionOther)
  230. const intersectionBase = new Set(["a", "b", "c", "d", "e"])
  231. const intersectionOther = {
  232. size: 3,
  233. has: () => { throw new Error("has should not be called") },
  234. keys: () => {
  235. intersectionBase.delete("b")
  236. intersectionBase.delete("c")
  237. intersectionBase.add("b")
  238. return ["x", "b", "b"]
  239. },
  240. }
  241. const intersection = intersectionBase.intersection(intersectionOther)
  242. const differenceBase = new Set(["a", "b", "c", "d", "e"])
  243. const differenceOther = {
  244. size: 3,
  245. has: () => { throw new Error("has should not be called") },
  246. keys: () => {
  247. differenceBase.delete("b")
  248. differenceBase.delete("c")
  249. differenceBase.add("b")
  250. return ["x", "b", "b"]
  251. },
  252. }
  253. const difference = differenceBase.difference(differenceOther)
  254. const symmetricBase = new Set(["a", "b", "c", "d", "e"])
  255. const symmetricOther = {
  256. size: 4,
  257. has: () => { throw new Error("has should not be called") },
  258. keys: () => {
  259. symmetricBase.delete("b")
  260. symmetricBase.delete("c")
  261. symmetricBase.add("b")
  262. return ["x", "b", "c", "c"]
  263. },
  264. }
  265. const symmetric = symmetricBase.symmetricDifference(symmetricOther)
  266. return [
  267. [...union], [...unionBase],
  268. [...intersection], [...intersectionBase],
  269. [...difference], [...differenceBase],
  270. [...symmetric], [...symmetricBase],
  271. ]
  272. `),
  273. ).toEqual([
  274. ["a", "b", "c", "d", "e", "x", "y"],
  275. ["a", "d", "e", "b"],
  276. ["b"],
  277. ["a", "d", "e", "b"],
  278. ["a", "c", "d", "e"],
  279. ["a", "d", "e", "b"],
  280. ["a", "c", "d", "e", "x"],
  281. ["a", "d", "e", "b"],
  282. ])
  283. })
  284. })
  285. describe("Set relation Test262 parity", () => {
  286. test("compares Sets", async () => {
  287. expect(
  288. await value(`
  289. const set = new Set([1, 2])
  290. return [
  291. set.isSubsetOf(new Set([2, 3])), set.isSubsetOf(new Set([1, 2, 3])),
  292. set.isSupersetOf(new Set([2, 3])), set.isSupersetOf(new Set([1])),
  293. set.isDisjointFrom(new Set([2, 3])), set.isDisjointFrom(new Set([3])),
  294. new Set().isSubsetOf(set), set.isSupersetOf(new Set()), new Set().isDisjointFrom(new Set()),
  295. set.isSubsetOf(set), set.isSupersetOf(set), set.isDisjointFrom(set),
  296. set.isSubsetOf(new Set([1, 2])), set.isSupersetOf(new Set([1, 2])),
  297. ]
  298. `),
  299. ).toEqual([false, true, false, true, false, true, true, true, true, true, true, false, true, true])
  300. })
  301. test("compares Maps and supported Set-like objects", async () => {
  302. expect(
  303. await value(`
  304. const set = new Set([1, 2])
  305. const map = new Map([[1, "one"], [2, "two"], [3, "three"]])
  306. const hasOnly = {
  307. size: 2,
  308. has: (value) => value === 1 || value === 2,
  309. keys: () => { throw new Error("keys should not be called") },
  310. }
  311. const keysOnly = {
  312. size: 1,
  313. has: () => { throw new Error("has should not be called") },
  314. keys: () => [1],
  315. }
  316. return [
  317. set.isSubsetOf(map), set.isSupersetOf(map), set.isDisjointFrom(map),
  318. set.isSubsetOf(hasOnly), set.isSupersetOf(keysOnly), set.isDisjointFrom(hasOnly),
  319. ]
  320. `),
  321. ).toEqual([true, false, false, true, true, false])
  322. })
  323. test("normalizes negative zero from Set-like keys", async () => {
  324. expect(
  325. await value(`
  326. const setlike = {
  327. size: 1,
  328. has: () => { throw new Error("has should not be called") },
  329. keys: () => [-0],
  330. }
  331. return [
  332. new Set([0, 1]).isSupersetOf(setlike),
  333. new Set([0, 1]).isDisjointFrom(setlike),
  334. ]
  335. `),
  336. ).toEqual([true, false])
  337. })
  338. test("observes live receiver mutation while calling Set-like has", async () => {
  339. expect(
  340. await value(`
  341. const subset = new Set(["a", "b", "c"])
  342. const subsetOther = {
  343. size: 3,
  344. has: (item) => {
  345. if (item === "a") subset.delete("c")
  346. return ["x", "a", "b"].includes(item)
  347. },
  348. keys: () => { throw new Error("keys should not be called") },
  349. }
  350. const disjoint = new Set(["a", "b", "c"])
  351. const disjointOther = {
  352. size: 3,
  353. has: (item) => {
  354. if (item === "a") {
  355. disjoint.delete("b")
  356. disjoint.delete("c")
  357. disjoint.add("b")
  358. }
  359. if (item === "c") throw new Error("deleted value should not be visited")
  360. return false
  361. },
  362. keys: () => { throw new Error("keys should not be called") },
  363. }
  364. return [
  365. subset.isSubsetOf(subsetOther), [...subset],
  366. disjoint.isDisjointFrom(disjointOther), [...disjoint],
  367. ]
  368. `),
  369. ).toEqual([true, ["a", "b"], true, ["a", "b"]])
  370. })
  371. })
  372. describe("Set-like validation Test262 parity", () => {
  373. test("rejects arrays, invalid sizes, and non-callable methods with TypeError", async () => {
  374. expect(
  375. await value(`
  376. const set = new Set([1])
  377. const names = []
  378. let coercionCalls = 0
  379. const invalid = [
  380. [],
  381. { size: undefined, has: () => false, keys: () => [] },
  382. { size: NaN, has: () => false, keys: () => [] },
  383. { size: { valueOf: () => { coercionCalls += 1; return NaN } }, has: () => false, keys: () => [] },
  384. { size: "string", has: () => false, keys: () => [] },
  385. { size: 0, has: undefined, keys: () => [] },
  386. { size: 0, has: {}, keys: () => [] },
  387. { size: 0, has: () => false, keys: undefined },
  388. { size: 0, has: () => false, keys: {} },
  389. ]
  390. for (const other of invalid) {
  391. try { set.union(other) } catch (error) { names.push(error.name) }
  392. }
  393. return [names, coercionCalls]
  394. `),
  395. ).toEqual([
  396. [
  397. "TypeError",
  398. "TypeError",
  399. "TypeError",
  400. "TypeError",
  401. "TypeError",
  402. "TypeError",
  403. "TypeError",
  404. "TypeError",
  405. "TypeError",
  406. ],
  407. 1,
  408. ])
  409. })
  410. test("validates Set-like records for every method", async () => {
  411. expect(
  412. await value(`
  413. const set = new Set([1])
  414. const invalid = { size: NaN, has: () => false, keys: () => [] }
  415. const names = []
  416. const operations = [
  417. () => set.union(invalid),
  418. () => set.intersection(invalid),
  419. () => set.difference(invalid),
  420. () => set.symmetricDifference(invalid),
  421. () => set.isSubsetOf(invalid),
  422. () => set.isSupersetOf(invalid),
  423. () => set.isDisjointFrom(invalid),
  424. ]
  425. for (const operation of operations) {
  426. try { operation() } catch (error) { names.push(error.name) }
  427. }
  428. return names
  429. `),
  430. ).toEqual(["TypeError", "TypeError", "TypeError", "TypeError", "TypeError", "TypeError", "TypeError"])
  431. })
  432. test("requires Set-like keys to return CodeMode's materialized iterator representation", async () => {
  433. expect(
  434. await value(`
  435. const set = new Set([1])
  436. const names = []
  437. for (const keys of [() => "ab", () => new Set([2]), () => new Map([[2, true]])]) {
  438. try { set.union({ size: 1, has: () => false, keys }) } catch (error) { names.push(error.name) }
  439. }
  440. return names
  441. `),
  442. ).toEqual(["TypeError", "TypeError", "TypeError"])
  443. })
  444. })