labeled-control-test262.test.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
  3. * - test/language/statements/labeled/continue.js
  4. * - test/language/statements/break/S12.8_A4_T1.js
  5. * - test/language/statements/continue/simple-and-labeled.js
  6. * - test/language/statements/continue/labeled-continue.js
  7. * - test/language/statements/continue/nested-let-bound-for-loops-labeled-continue.js
  8. *
  9. * Copyright (C) 2009 the Sputnik authors. All rights reserved.
  10. * Copyright (C) 2014, 2016 the V8 project authors. All rights reserved.
  11. * Test262 portions are governed by the BSD license in LICENSE.test262.
  12. */
  13. import { describe, expect, test } from "bun:test"
  14. import { Effect } from "effect"
  15. import { CodeMode } from "../src/index.js"
  16. const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  17. const value = async (code: string) => {
  18. const result = await execute(code)
  19. if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  20. return result.value
  21. }
  22. describe("Test262 labeled break adaptations", () => {
  23. test("break exits the matching labeled statement", async () => {
  24. expect(
  25. await value(`
  26. const visited = []
  27. target: {
  28. visited.push(1)
  29. break target
  30. visited.push(2)
  31. }
  32. visited.push(3)
  33. return visited
  34. `),
  35. ).toEqual([1, 3])
  36. })
  37. test("break crosses nested loops and switches without being consumed early", async () => {
  38. expect(
  39. await value(`
  40. const visited = []
  41. outer: for (const item of [1, 2, 3]) {
  42. switch (item) {
  43. case 2:
  44. break outer
  45. default:
  46. visited.push(item)
  47. }
  48. }
  49. return visited
  50. `),
  51. ).toEqual([1])
  52. })
  53. test("break targets every supported loop form", async () => {
  54. expect(
  55. await value(`
  56. const counts = { while: 0, doWhile: 0, for: 0, forOf: 0, forIn: 0 }
  57. whileLabel: while (true) {
  58. counts.while++
  59. break whileLabel
  60. }
  61. doLabel: do {
  62. counts.doWhile++
  63. break doLabel
  64. } while (true)
  65. forLabel: for (;;) {
  66. counts.for++
  67. break forLabel
  68. }
  69. forOfLabel: for (const item of [1, 2]) {
  70. counts.forOf++
  71. break forOfLabel
  72. }
  73. forInLabel: for (const key in { a: 1, b: 2 }) {
  74. counts.forIn++
  75. break forInLabel
  76. }
  77. return counts
  78. `),
  79. ).toEqual({ while: 1, doWhile: 1, for: 1, forOf: 1, forIn: 1 })
  80. })
  81. test("nested labels consume only their matching break", async () => {
  82. expect(
  83. await value(`
  84. const visited = []
  85. outer: {
  86. inner: {
  87. visited.push(1)
  88. break outer
  89. }
  90. visited.push(2)
  91. }
  92. visited.push(3)
  93. return visited
  94. `),
  95. ).toEqual([1, 3])
  96. })
  97. })
  98. describe("Test262 labeled continue adaptations", () => {
  99. test("continue targets its labeled for loop", async () => {
  100. expect(
  101. await value(`
  102. let count = 0
  103. label: for (let index = 0; index < 10; index++) {
  104. count++
  105. continue label
  106. }
  107. return count
  108. `),
  109. ).toBe(10)
  110. })
  111. test("continue escapes an inner loop for the targeted outer loop", async () => {
  112. expect(
  113. await value(`
  114. let count = 0
  115. outer: for (let x = 0; x < 10; x++) {
  116. let y = 0
  117. while (true) {
  118. y++
  119. count++
  120. if (y === 1) continue outer
  121. throw new Error("inner loop consumed the outer continue")
  122. }
  123. }
  124. return count
  125. `),
  126. ).toBe(10)
  127. })
  128. test("multiple labels can target the same iteration statement", async () => {
  129. expect(
  130. await value(`
  131. const visited = []
  132. outer: inner: for (const item of [1, 2, 3]) {
  133. visited.push(item)
  134. continue outer
  135. }
  136. return visited
  137. `),
  138. ).toEqual([1, 2, 3])
  139. })
  140. test("labeled continue works for every supported loop form", async () => {
  141. expect(
  142. await value(`
  143. const counts = { while: 0, doWhile: 0, forOf: 0, forIn: 0 }
  144. let index = 0
  145. whileLabel: while (index++ < 2) {
  146. counts.while++
  147. continue whileLabel
  148. }
  149. index = 0
  150. doLabel: do {
  151. counts.doWhile++
  152. if (index++ < 1) continue doLabel
  153. } while (index < 2)
  154. forOfLabel: for (const item of [1, 2]) {
  155. counts.forOf += item
  156. continue forOfLabel
  157. }
  158. forInLabel: for (const key in { a: 1, b: 2 }) {
  159. counts.forIn++
  160. continue forInLabel
  161. }
  162. return counts
  163. `),
  164. ).toEqual({ while: 2, doWhile: 2, forOf: 3, forIn: 2 })
  165. })
  166. test("labeled continues preserve per-iteration lexical bindings", async () => {
  167. expect(
  168. await value(`
  169. const classic = []
  170. classicLabel: for (let index = 0; index < 3; index++) {
  171. classic.push(() => index)
  172. continue classicLabel
  173. }
  174. const nested = []
  175. nestedLabel: for (let outer = 0; outer < 3; outer++) {
  176. for (let inner = 0; inner < 2; inner++) {
  177. nested.push(() => outer)
  178. continue nestedLabel
  179. }
  180. }
  181. const forOf = []
  182. forOfLabel: for (const item of [1, 2, 3]) {
  183. forOf.push(() => item)
  184. continue forOfLabel
  185. }
  186. return [classic.map((read) => read()), nested.map((read) => read()), forOf.map((read) => read())]
  187. `),
  188. ).toEqual([
  189. [0, 1, 2],
  190. [0, 1, 2],
  191. [1, 2, 3],
  192. ])
  193. })
  194. test("finally completions override labeled loop control", async () => {
  195. expect(
  196. await value(`
  197. const visited = []
  198. breakWins: for (const item of [1, 2, 3]) {
  199. try {
  200. continue breakWins
  201. } finally {
  202. visited.push(item)
  203. break breakWins
  204. }
  205. }
  206. continueWins: for (const item of [4, 5, 6]) {
  207. try {
  208. break continueWins
  209. } finally {
  210. visited.push(item)
  211. continue continueWins
  212. }
  213. }
  214. return visited
  215. `),
  216. ).toEqual([1, 4, 5, 6])
  217. })
  218. test("a label on a non-iteration statement is not a continue target", async () => {
  219. const result = await execute(`
  220. do {
  221. target: {
  222. continue target
  223. }
  224. } while (false)
  225. `)
  226. expect(result.ok).toBe(false)
  227. if (result.ok) return
  228. expect(result.error.kind).toBe("ParseError")
  229. })
  230. })