for-await-test262.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /*
  2. * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
  3. * - test/language/statements/for-await-of/ticks-with-sync-iter-resolved-promise-and-constructor-lookup.js
  4. * - test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup.js
  5. * - test/language/statements/for-await-of/async-func-dstr-let-ary-ptrn-elem-id-iter-val.js
  6. * - test/language/statements/for-await-of/async-func-decl-dstr-array-rest-after-element.js
  7. * - test/language/statements/for-await-of/iterator-close-non-throw-get-method-is-null.js
  8. * - test/language/statements/for-await-of/iterator-close-non-throw-get-method-non-callable.js
  9. * - test/language/statements/for-await-of/iterator-close-throw-get-method-non-callable.js
  10. *
  11. * Copyright (C) 2019 André Bargull. All rights reserved.
  12. * Copyright (C) 2020 Alexey Shvayka. All rights reserved.
  13. * Test262 portions are governed by the BSD license in LICENSE.test262.
  14. */
  15. import { describe, expect, test } from "bun:test"
  16. import { Effect } from "effect"
  17. import { CodeMode } from "../src/index.js"
  18. const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  19. const value = async (code: string) => {
  20. const result = await execute(code)
  21. if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  22. return result.value
  23. }
  24. describe("Test262 for-await-of adaptations", () => {
  25. test("awaits promise and plain values from a synchronous array", async () => {
  26. expect(
  27. await value(`
  28. const values = []
  29. for await (const item of [Promise.resolve(1), 2, new Promise((resolve) => resolve(3))]) {
  30. values.push(item)
  31. }
  32. return values
  33. `),
  34. ).toEqual([1, 2, 3])
  35. })
  36. test("defers the body even for an already-resolved or plain value", async () => {
  37. expect(
  38. await value(`
  39. const events = []
  40. const before = Promise.resolve().then(() => events.push("before"))
  41. for await (const item of [Promise.resolve(1), 2]) events.push("body " + item)
  42. await before
  43. return events
  44. `),
  45. ).toEqual(["before", "body 1", "body 2"])
  46. })
  47. test("an awaited rejection exits through normal try/catch", async () => {
  48. const result = await execute(`
  49. const values = []
  50. try {
  51. for await (const item of [Promise.resolve(1), Promise.reject("stop"), Promise.resolve(3)]) {
  52. values.push(item)
  53. }
  54. } catch (error) {
  55. return [values, error]
  56. }
  57. return "missed"
  58. `)
  59. expect(result.ok).toBe(true)
  60. if (!result.ok) return
  61. expect(result.value).toEqual([[1], "stop"])
  62. expect(result.warnings ?? []).toEqual([])
  63. })
  64. test("destructures after resolving each yielded value", async () => {
  65. expect(
  66. await value(`
  67. const values = []
  68. for await (const [first, ...rest] of [Promise.resolve([1, 2, 3])]) {
  69. values.push(first, rest)
  70. }
  71. return values
  72. `),
  73. ).toEqual([1, [2, 3]])
  74. })
  75. test("supports assignment targets", async () => {
  76. expect(
  77. await value(`
  78. let first
  79. let rest
  80. for await ([first, ...rest] of [Promise.resolve([1, 2, 3])]) {}
  81. return [first, rest]
  82. `),
  83. ).toEqual([1, [2, 3]])
  84. })
  85. test("preserves fresh lexical bindings per iteration", async () => {
  86. expect(
  87. await value(`
  88. const reads = []
  89. for await (const item of [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]) {
  90. reads.push(() => item)
  91. }
  92. return reads.map((read) => read())
  93. `),
  94. ).toEqual([1, 2, 3])
  95. })
  96. test("supports every existing collection iterable", async () => {
  97. expect(
  98. await value(`
  99. const string = []
  100. for await (const item of "ab") string.push(item)
  101. const set = []
  102. for await (const item of new Set([Promise.resolve(1), 2])) set.push(item)
  103. const map = []
  104. for await (const [key, item] of new Map([["a", 1], ["b", 2]])) map.push(key, item)
  105. const params = []
  106. for await (const [key, item] of new URLSearchParams("a=1&b=2")) params.push(key, item)
  107. return { string, set, map, params }
  108. `),
  109. ).toEqual({ string: ["a", "b"], set: [1, 2], map: ["a", 1, "b", 2], params: ["a", "1", "b", "2"] })
  110. })
  111. test("preserves labeled break and continue behavior", async () => {
  112. expect(
  113. await value(`
  114. const values = []
  115. outer: for await (const item of [1, 2, 3, 4]) {
  116. if (item === 2) continue outer
  117. if (item === 4) break outer
  118. values.push(item)
  119. }
  120. return values
  121. `),
  122. ).toEqual([1, 3])
  123. })
  124. test("drives a custom async iterator sequentially", async () => {
  125. expect(
  126. await value(`
  127. let index = 0
  128. const iterator = {
  129. [Symbol.asyncIterator]: () => iterator,
  130. async next() {
  131. index += 1
  132. if (index > 3) return { done: true }
  133. return { done: false, value: index }
  134. },
  135. }
  136. const values = []
  137. for await (const item of iterator) values.push(item)
  138. return values
  139. `),
  140. ).toEqual([1, 2, 3])
  141. })
  142. test("leaves async iterator values under the iterator's control", async () => {
  143. expect(
  144. await value(`
  145. let done = false
  146. const iterator = {
  147. [Symbol.asyncIterator]: () => iterator,
  148. next: async () => done ? { done: true } : (done = true, { done: false, value: Promise.resolve(1) }),
  149. }
  150. for await (const item of iterator) return [item instanceof Promise, await item]
  151. `),
  152. ).toEqual([true, 1])
  153. })
  154. test("awaits synchronous results from an async iterator before the body", async () => {
  155. expect(
  156. await value(`
  157. const events = ["pre"]
  158. const ticks = Promise.resolve()
  159. .then(() => events.push("tick 1"))
  160. .then(() => events.push("tick 2"))
  161. let done = false
  162. const iterator = {
  163. [Symbol.asyncIterator]: () => iterator,
  164. next: () => done ? { done: true } : (done = true, { done: false, value: Promise.resolve(1) }),
  165. }
  166. for await (const item of iterator) events.push(item instanceof Promise ? "loop" : "adopted")
  167. events.push("post")
  168. await ticks
  169. return events
  170. `),
  171. ).toEqual(["pre", "tick 1", "loop", "tick 2", "post"])
  172. })
  173. test("falls back to a custom synchronous iterator", async () => {
  174. expect(
  175. await value(`
  176. let index = 0
  177. const iterator = {
  178. [Symbol.iterator]: () => iterator,
  179. next() {
  180. index += 1
  181. return index > 2 ? { done: true } : { done: false, value: Promise.resolve(index) }
  182. },
  183. }
  184. const values = []
  185. for await (const item of iterator) values.push(item)
  186. return values
  187. `),
  188. ).toEqual([1, 2])
  189. })
  190. test("adopts terminal and close values from synchronous iterators", async () => {
  191. expect(
  192. await value(`
  193. const terminal = {
  194. [Symbol.iterator]: () => terminal,
  195. next: () => ({ done: true, value: Promise.reject("terminal") }),
  196. }
  197. let terminalError
  198. try {
  199. for await (const item of terminal) {}
  200. } catch (error) {
  201. terminalError = error
  202. }
  203. const closing = {
  204. [Symbol.iterator]: () => closing,
  205. next: () => ({ done: false, value: 1 }),
  206. return: () => ({ done: true, value: Promise.reject("close") }),
  207. }
  208. let closeError
  209. try {
  210. for await (const item of closing) break
  211. } catch (error) {
  212. closeError = error
  213. }
  214. return [terminalError, closeError]
  215. `),
  216. ).toEqual(["terminal", "close"])
  217. })
  218. test("captures the next method when acquiring the iterator", async () => {
  219. expect(
  220. await value(`
  221. let count = 0
  222. const next = () => {
  223. count += 1
  224. if (count === 1) iterator.next = () => ({ done: true })
  225. return count > 2 ? { done: true } : { done: false, value: count }
  226. }
  227. const iterator = { [Symbol.iterator]: () => iterator, next }
  228. const values = []
  229. for await (const item of iterator) values.push(item)
  230. return values
  231. `),
  232. ).toEqual([1, 2])
  233. })
  234. test("captures synchronous iterator result fields before suspending", async () => {
  235. expect(
  236. await value(`
  237. let count = 0
  238. const result = { done: false, value: 1 }
  239. const iterator = {
  240. [Symbol.iterator]: () => iterator,
  241. next() {
  242. count += 1
  243. if (count > 1) return { done: true }
  244. Promise.resolve().then(() => {
  245. result.done = true
  246. result.value = 2
  247. })
  248. return result
  249. },
  250. }
  251. const values = []
  252. for await (const item of iterator) values.push(item)
  253. return values
  254. `),
  255. ).toEqual([1])
  256. })
  257. test("preserves the async close turn for a sync iterator without return", async () => {
  258. expect(
  259. await value(`
  260. const events = []
  261. const iterator = {
  262. [Symbol.iterator]: () => iterator,
  263. next: () => ({ done: false, value: 1 }),
  264. }
  265. for await (const item of iterator) {
  266. Promise.resolve().then(() => events.push("reaction"))
  267. break
  268. }
  269. events.push("after")
  270. return events
  271. `),
  272. ).toEqual(["reaction", "after"])
  273. })
  274. test("defers synchronous iterator protocol errors", async () => {
  275. expect(
  276. await value(`
  277. const events = []
  278. const throwing = {
  279. [Symbol.iterator]: () => throwing,
  280. next() {
  281. Promise.resolve().then(() => events.push("next reaction"))
  282. throw "next"
  283. },
  284. }
  285. try {
  286. for await (const item of throwing) {}
  287. } catch (error) {
  288. events.push("next catch")
  289. }
  290. const malformed = {
  291. [Symbol.iterator]: () => malformed,
  292. next() {
  293. Promise.resolve().then(() => events.push("result reaction"))
  294. return 1
  295. },
  296. }
  297. try {
  298. for await (const item of malformed) {}
  299. } catch (error) {
  300. events.push("result catch")
  301. }
  302. const closing = {
  303. [Symbol.iterator]: () => closing,
  304. next: () => ({ done: false, value: 1 }),
  305. return() {
  306. Promise.resolve().then(() => events.push("return reaction"))
  307. throw "return"
  308. },
  309. }
  310. try {
  311. for await (const item of closing) break
  312. } catch (error) {
  313. events.push("return catch")
  314. }
  315. return events
  316. `),
  317. ).toEqual(["next reaction", "next catch", "result reaction", "result catch", "return reaction", "return catch"])
  318. })
  319. test("prefers Symbol.asyncIterator over Symbol.iterator", async () => {
  320. expect(
  321. await value(`
  322. let done = false
  323. const asyncIterator = {
  324. next: async () => done ? { done: true } : (done = true, { done: false, value: "async" }),
  325. }
  326. const syncIterator = { next: () => ({ done: true }) }
  327. const iterable = {
  328. [Symbol.asyncIterator]: () => asyncIterator,
  329. [Symbol.iterator]: () => syncIterator,
  330. }
  331. const values = []
  332. for await (const item of iterable) values.push(item)
  333. return values
  334. `),
  335. ).toEqual(["async"])
  336. })
  337. test("closes a custom iterator on abrupt loop completion", async () => {
  338. expect(
  339. await value(`
  340. let closed = 0
  341. const iterator = {
  342. [Symbol.asyncIterator]: () => iterator,
  343. next: async () => ({ done: false, value: 1 }),
  344. return: async () => (closed += 1, { done: true }),
  345. }
  346. for await (const item of iterator) break
  347. try {
  348. for await (const item of iterator) throw "stop"
  349. } catch (error) {}
  350. return closed
  351. `),
  352. ).toBe(2)
  353. })
  354. test("treats a null iterator return method as absent", async () => {
  355. expect(
  356. await value(`
  357. let count = 0
  358. const iterator = {
  359. [Symbol.asyncIterator]: () => iterator,
  360. next: async () => ({ done: false, value: 1 }),
  361. return: null,
  362. }
  363. for await (const item of iterator) {
  364. count += 1
  365. break
  366. }
  367. return count
  368. `),
  369. ).toBe(1)
  370. })
  371. test("a non-callable return method replaces break with TypeError", async () => {
  372. expect(
  373. await value(`
  374. const iterator = {
  375. [Symbol.asyncIterator]: () => iterator,
  376. next: async () => ({ done: false, value: 1 }),
  377. return: true,
  378. }
  379. try {
  380. for await (const item of iterator) break
  381. } catch (error) {
  382. return error.name
  383. }
  384. return "missed"
  385. `),
  386. ).toBe("TypeError")
  387. })
  388. test("a body throw wins over a non-callable return method", async () => {
  389. expect(
  390. await value(`
  391. const iterator = {
  392. [Symbol.asyncIterator]: () => iterator,
  393. next: async () => ({ done: false, value: 1 }),
  394. return: true,
  395. }
  396. try {
  397. for await (const item of iterator) throw "body"
  398. } catch (error) {
  399. return error
  400. }
  401. return "missed"
  402. `),
  403. ).toBe("body")
  404. })
  405. test("rejects a primitive iterator return result", async () => {
  406. expect(
  407. await value(`
  408. const iterator = {
  409. [Symbol.asyncIterator]: () => iterator,
  410. next: async () => ({ done: false, value: 1 }),
  411. return: async () => null,
  412. }
  413. try {
  414. for await (const item of iterator) break
  415. } catch (error) {
  416. return error.name
  417. }
  418. return "missed"
  419. `),
  420. ).toBe("TypeError")
  421. })
  422. test("propagates iterator acquisition failures", async () => {
  423. expect(
  424. await value(`
  425. const iterator = {
  426. [Symbol.asyncIterator]: () => { throw "acquire" },
  427. }
  428. try {
  429. for await (const item of iterator) {}
  430. } catch (error) {
  431. return error
  432. }
  433. return "missed"
  434. `),
  435. ).toBe("acquire")
  436. })
  437. test("rejects malformed iterator acquisition methods and results", async () => {
  438. expect(
  439. await value(`
  440. const names = []
  441. const invalid = [
  442. { [Symbol.asyncIterator]: true },
  443. { [Symbol.asyncIterator]: () => 1 },
  444. { [Symbol.asyncIterator]: () => ({ next: true }) },
  445. ]
  446. for (const iterator of invalid) {
  447. try {
  448. for await (const item of iterator) {}
  449. } catch (error) {
  450. names.push(error.name)
  451. }
  452. }
  453. return names
  454. `),
  455. ).toEqual(["TypeError", "TypeError", "TypeError"])
  456. })
  457. test("preserves iterator protocol keys through object copies", async () => {
  458. expect(
  459. await value(`
  460. let done = false
  461. const iterable = {
  462. plain: true,
  463. [Symbol.asyncIterator]: () => iterable,
  464. next: async () => done ? { done: true } : (done = true, { done: false, value: 1 }),
  465. }
  466. const spread = { ...iterable }
  467. const { plain, ...rest } = iterable
  468. const assigned = Object.assign({}, iterable)
  469. const values = []
  470. for await (const item of spread) values.push(item)
  471. return [
  472. values,
  473. Object.hasOwn(spread, Symbol.asyncIterator),
  474. Object.hasOwn(rest, Symbol.asyncIterator),
  475. Object.hasOwn(assigned, Symbol.asyncIterator),
  476. ]
  477. `),
  478. ).toEqual([[1], true, true, true])
  479. })
  480. test("rejects malformed iterator protocol results", async () => {
  481. const result = await execute(`
  482. const iterator = {
  483. [Symbol.asyncIterator]: () => iterator,
  484. next: async () => 1,
  485. }
  486. for await (const item of iterator) {}
  487. `)
  488. expect(result.ok).toBe(false)
  489. if (result.ok) return
  490. expect(result.error.message).toContain("Iterator next() result must be an object")
  491. })
  492. test("rejects objects without an iterator protocol method", async () => {
  493. const result = await execute(`for await (const item of { values: [1, 2] }) {}`)
  494. expect(result.ok).toBe(false)
  495. if (result.ok) return
  496. expect(result.error.message).toContain("or custom iterator value")
  497. })
  498. })