goscript 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. package/cmd/goscript/cmd-test.go +104 -11
  2. package/cmd/goscript/cmd-test_test.go +1 -1
  3. package/cmd/goscript/cmd_compile.go +9 -0
  4. package/compiler/compile-request.go +31 -0
  5. package/compiler/compiler.go +1 -1
  6. package/compiler/compliance_test.go +0 -2
  7. package/compiler/config.go +2 -0
  8. package/compiler/gotest/package-result.go +2 -0
  9. package/compiler/gotest/request.go +85 -20
  10. package/compiler/gotest/runner.go +733 -96
  11. package/compiler/gotest/runner_test.go +647 -3
  12. package/compiler/lowered-program.go +9 -2
  13. package/compiler/lowering.go +2001 -345
  14. package/compiler/override-facts.go +77 -27
  15. package/compiler/override-registry.go +5 -4
  16. package/compiler/override-registry_test.go +135 -0
  17. package/compiler/package-graph_test.go +62 -7
  18. package/compiler/package-test-graph-variant.go +40 -16
  19. package/compiler/package-test-graph.go +0 -5
  20. package/compiler/package-test-graph_test.go +61 -3
  21. package/compiler/runtime-contract.go +40 -0
  22. package/compiler/semantic-model-types.go +16 -0
  23. package/compiler/semantic-model.go +336 -91
  24. package/compiler/semantic-model_test.go +50 -1
  25. package/compiler/service.go +9 -3
  26. package/compiler/skeleton_test.go +1921 -298
  27. package/compiler/tsworkspace/owner-process-unix_test.go +72 -0
  28. package/compiler/tsworkspace/owner.go +8 -0
  29. package/compiler/tsworkspace/tool-process-other.go +14 -0
  30. package/compiler/tsworkspace/tool-process-unix.go +19 -0
  31. package/compiler/typescript-emitter.go +122 -9
  32. package/dist/gs/builtin/builtin.d.ts +20 -1
  33. package/dist/gs/builtin/builtin.js +246 -26
  34. package/dist/gs/builtin/builtin.js.map +1 -1
  35. package/dist/gs/builtin/channel.d.ts +24 -10
  36. package/dist/gs/builtin/channel.js +107 -25
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/defer.d.ts +1 -0
  39. package/dist/gs/builtin/defer.js +12 -2
  40. package/dist/gs/builtin/defer.js.map +1 -1
  41. package/dist/gs/builtin/hostio.d.ts +9 -0
  42. package/dist/gs/builtin/hostio.js +25 -0
  43. package/dist/gs/builtin/hostio.js.map +1 -1
  44. package/dist/gs/builtin/map.js +40 -6
  45. package/dist/gs/builtin/map.js.map +1 -1
  46. package/dist/gs/builtin/print.js.map +1 -1
  47. package/dist/gs/builtin/slice.d.ts +43 -9
  48. package/dist/gs/builtin/slice.js +437 -234
  49. package/dist/gs/builtin/slice.js.map +1 -1
  50. package/dist/gs/builtin/type.d.ts +2 -0
  51. package/dist/gs/builtin/type.js +47 -7
  52. package/dist/gs/builtin/type.js.map +1 -1
  53. package/dist/gs/builtin/varRef.d.ts +2 -0
  54. package/dist/gs/builtin/varRef.js.map +1 -1
  55. package/dist/gs/bytes/buffer.gs.js +28 -28
  56. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  57. package/dist/gs/bytes/iter.gs.js +13 -13
  58. package/dist/gs/bytes/iter.gs.js.map +1 -1
  59. package/dist/gs/compress/zlib/index.d.ts +26 -0
  60. package/dist/gs/compress/zlib/index.js +168 -0
  61. package/dist/gs/compress/zlib/index.js.map +1 -0
  62. package/dist/gs/context/context.d.ts +1 -1
  63. package/dist/gs/context/context.js +8 -3
  64. package/dist/gs/context/context.js.map +1 -1
  65. package/dist/gs/crypto/ecdh/index.d.ts +52 -0
  66. package/dist/gs/crypto/ecdh/index.js +226 -0
  67. package/dist/gs/crypto/ecdh/index.js.map +1 -0
  68. package/dist/gs/crypto/ed25519/index.d.ts +34 -0
  69. package/dist/gs/crypto/ed25519/index.js +160 -0
  70. package/dist/gs/crypto/ed25519/index.js.map +1 -0
  71. package/dist/gs/crypto/internal/constanttime/index.d.ts +4 -0
  72. package/dist/gs/crypto/internal/constanttime/index.js +18 -0
  73. package/dist/gs/crypto/internal/constanttime/index.js.map +1 -0
  74. package/dist/gs/crypto/rand/index.d.ts +2 -0
  75. package/dist/gs/crypto/rand/index.js +85 -0
  76. package/dist/gs/crypto/rand/index.js.map +1 -1
  77. package/dist/gs/crypto/sha256/index.d.ts +8 -0
  78. package/dist/gs/crypto/sha256/index.js +118 -0
  79. package/dist/gs/crypto/sha256/index.js.map +1 -0
  80. package/dist/gs/crypto/sha512/index.d.ts +14 -0
  81. package/dist/gs/crypto/sha512/index.js +129 -0
  82. package/dist/gs/crypto/sha512/index.js.map +1 -0
  83. package/dist/gs/encoding/json/index.d.ts +3 -0
  84. package/dist/gs/encoding/json/index.js +15 -0
  85. package/dist/gs/encoding/json/index.js.map +1 -1
  86. package/dist/gs/errors/errors.js +29 -6
  87. package/dist/gs/errors/errors.js.map +1 -1
  88. package/dist/gs/fmt/fmt.js.map +1 -1
  89. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +7 -7
  90. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +52 -18
  91. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  92. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +56 -20
  93. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  94. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.d.ts +57 -3
  95. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js +366 -1
  96. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js.map +1 -1
  97. package/dist/gs/github.com/aperturerobotics/util/conc/index.d.ts +20 -0
  98. package/dist/gs/github.com/aperturerobotics/util/conc/index.js +134 -0
  99. package/dist/gs/github.com/aperturerobotics/util/conc/index.js.map +1 -0
  100. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  101. package/dist/gs/github.com/hack-pad/safejs/internal/catch/index.d.ts +3 -0
  102. package/dist/gs/github.com/hack-pad/safejs/internal/catch/index.js +50 -0
  103. package/dist/gs/github.com/hack-pad/safejs/internal/catch/index.js.map +1 -0
  104. package/dist/gs/github.com/klauspost/compress/internal/le/index.js +3 -2
  105. package/dist/gs/github.com/klauspost/compress/internal/le/index.js.map +1 -1
  106. package/dist/gs/github.com/mr-tron/base58/base58/index.d.ts +27 -0
  107. package/dist/gs/github.com/mr-tron/base58/base58/index.js +172 -0
  108. package/dist/gs/github.com/mr-tron/base58/base58/index.js.map +1 -0
  109. package/dist/gs/github.com/zeebo/blake3/internal/consts/index.d.ts +21 -0
  110. package/dist/gs/github.com/zeebo/blake3/internal/consts/index.js +22 -0
  111. package/dist/gs/github.com/zeebo/blake3/internal/consts/index.js.map +1 -0
  112. package/dist/gs/go/token/index.js +11 -4
  113. package/dist/gs/go/token/index.js.map +1 -1
  114. package/dist/gs/hash/fnv/index.d.ts +57 -0
  115. package/dist/gs/hash/fnv/index.js +299 -0
  116. package/dist/gs/hash/fnv/index.js.map +1 -0
  117. package/dist/gs/hash/index.d.ts +17 -0
  118. package/dist/gs/hash/index.js +94 -0
  119. package/dist/gs/hash/index.js.map +1 -0
  120. package/dist/gs/io/fs/readlink.js +2 -6
  121. package/dist/gs/io/fs/readlink.js.map +1 -1
  122. package/dist/gs/io/fs/walk.js.map +1 -1
  123. package/dist/gs/io/io.js.map +1 -1
  124. package/dist/gs/iter/iter.d.ts +3 -2
  125. package/dist/gs/iter/iter.js.map +1 -1
  126. package/dist/gs/maps/iter.d.ts +5 -5
  127. package/dist/gs/maps/iter.js +48 -21
  128. package/dist/gs/maps/iter.js.map +1 -1
  129. package/dist/gs/maps/maps.d.ts +6 -6
  130. package/dist/gs/math/bits/index.js +14 -24
  131. package/dist/gs/math/bits/index.js.map +1 -1
  132. package/dist/gs/mime/index.js +3 -1
  133. package/dist/gs/mime/index.js.map +1 -1
  134. package/dist/gs/net/http/httptest/index.d.ts +20 -1
  135. package/dist/gs/net/http/httptest/index.js +83 -3
  136. package/dist/gs/net/http/httptest/index.js.map +1 -1
  137. package/dist/gs/net/http/index.d.ts +110 -6
  138. package/dist/gs/net/http/index.js +262 -16
  139. package/dist/gs/net/http/index.js.map +1 -1
  140. package/dist/gs/net/http/pprof/index.d.ts +8 -0
  141. package/dist/gs/net/http/pprof/index.js +59 -0
  142. package/dist/gs/net/http/pprof/index.js.map +1 -0
  143. package/dist/gs/os/error.gs.js +9 -7
  144. package/dist/gs/os/error.gs.js.map +1 -1
  145. package/dist/gs/os/types_js.gs.js +95 -15
  146. package/dist/gs/os/types_js.gs.js.map +1 -1
  147. package/dist/gs/path/filepath/match.js.map +1 -1
  148. package/dist/gs/path/filepath/path.d.ts +5 -3
  149. package/dist/gs/path/filepath/path.js +65 -10
  150. package/dist/gs/path/filepath/path.js.map +1 -1
  151. package/dist/gs/reflect/index.d.ts +3 -2
  152. package/dist/gs/reflect/index.js +2 -1
  153. package/dist/gs/reflect/index.js.map +1 -1
  154. package/dist/gs/reflect/iter.js +2 -2
  155. package/dist/gs/reflect/iter.js.map +1 -1
  156. package/dist/gs/reflect/map.js +26 -0
  157. package/dist/gs/reflect/map.js.map +1 -1
  158. package/dist/gs/reflect/type.d.ts +24 -5
  159. package/dist/gs/reflect/type.js +390 -38
  160. package/dist/gs/reflect/type.js.map +1 -1
  161. package/dist/gs/reflect/types.d.ts +1 -0
  162. package/dist/gs/reflect/types.js +3 -1
  163. package/dist/gs/reflect/types.js.map +1 -1
  164. package/dist/gs/reflect/value.d.ts +4 -1
  165. package/dist/gs/reflect/value.js +39 -1
  166. package/dist/gs/reflect/value.js.map +1 -1
  167. package/dist/gs/reflect/visiblefields.js +1 -1
  168. package/dist/gs/reflect/visiblefields.js.map +1 -1
  169. package/dist/gs/runtime/debug/index.d.ts +39 -0
  170. package/dist/gs/runtime/debug/index.js +58 -0
  171. package/dist/gs/runtime/debug/index.js.map +1 -1
  172. package/dist/gs/runtime/pprof/index.d.ts +20 -0
  173. package/dist/gs/runtime/pprof/index.js +85 -0
  174. package/dist/gs/runtime/pprof/index.js.map +1 -0
  175. package/dist/gs/runtime/trace/index.d.ts +19 -0
  176. package/dist/gs/runtime/trace/index.js +64 -0
  177. package/dist/gs/runtime/trace/index.js.map +1 -0
  178. package/dist/gs/slices/slices.d.ts +24 -9
  179. package/dist/gs/slices/slices.js +229 -24
  180. package/dist/gs/slices/slices.js.map +1 -1
  181. package/dist/gs/sort/slice.gs.d.ts +5 -3
  182. package/dist/gs/sort/slice.gs.js +55 -17
  183. package/dist/gs/sort/slice.gs.js.map +1 -1
  184. package/dist/gs/strings/builder.js +26 -17
  185. package/dist/gs/strings/builder.js.map +1 -1
  186. package/dist/gs/strings/iter.js +140 -75
  187. package/dist/gs/strings/iter.js.map +1 -1
  188. package/dist/gs/strings/replace.js +2 -2
  189. package/dist/gs/strings/replace.js.map +1 -1
  190. package/dist/gs/strings/strings.js +52 -6
  191. package/dist/gs/strings/strings.js.map +1 -1
  192. package/dist/gs/sync/sync.d.ts +6 -3
  193. package/dist/gs/sync/sync.js +39 -11
  194. package/dist/gs/sync/sync.js.map +1 -1
  195. package/dist/gs/syscall/errors.d.ts +116 -112
  196. package/dist/gs/syscall/errors.js +38 -1
  197. package/dist/gs/syscall/errors.js.map +1 -1
  198. package/dist/gs/syscall/fs.d.ts +2 -8
  199. package/dist/gs/syscall/fs.js.map +1 -1
  200. package/dist/gs/syscall/js/index.js +20 -12
  201. package/dist/gs/syscall/js/index.js.map +1 -1
  202. package/dist/gs/syscall/types.d.ts +4 -1
  203. package/dist/gs/syscall/types.js.map +1 -1
  204. package/dist/gs/testing/testing.d.ts +4 -3
  205. package/dist/gs/testing/testing.js +21 -4
  206. package/dist/gs/testing/testing.js.map +1 -1
  207. package/dist/gs/time/time.js +22 -0
  208. package/dist/gs/time/time.js.map +1 -1
  209. package/dist/gs/unicode/unicode.js.map +1 -1
  210. package/dist/gs/unique/index.js +7 -2
  211. package/dist/gs/unique/index.js.map +1 -1
  212. package/go.mod +8 -8
  213. package/go.sum +14 -23
  214. package/gs/builtin/builtin.ts +364 -37
  215. package/gs/builtin/channel.ts +161 -29
  216. package/gs/builtin/defer.ts +13 -2
  217. package/gs/builtin/hostio.test.ts +1 -0
  218. package/gs/builtin/hostio.ts +38 -0
  219. package/gs/builtin/map.ts +46 -6
  220. package/gs/builtin/print.ts +12 -3
  221. package/gs/builtin/runtime-contract.test.ts +257 -10
  222. package/gs/builtin/slice.test.ts +70 -0
  223. package/gs/builtin/slice.ts +566 -255
  224. package/gs/builtin/type.ts +53 -9
  225. package/gs/builtin/varRef.ts +2 -0
  226. package/gs/bytes/buffer.gs.ts +28 -28
  227. package/gs/bytes/iter.gs.ts +13 -14
  228. package/gs/compress/zlib/index.test.ts +28 -0
  229. package/gs/compress/zlib/index.ts +200 -0
  230. package/gs/compress/zlib/meta.json +3 -0
  231. package/gs/context/context.test.ts +31 -1
  232. package/gs/context/context.ts +9 -4
  233. package/gs/crypto/ecdh/index.test.ts +43 -0
  234. package/gs/crypto/ecdh/index.ts +274 -0
  235. package/gs/crypto/ed25519/index.test.ts +41 -0
  236. package/gs/crypto/ed25519/index.ts +238 -0
  237. package/gs/crypto/ed25519/meta.json +13 -0
  238. package/gs/crypto/internal/constanttime/index.test.ts +25 -0
  239. package/gs/crypto/internal/constanttime/index.ts +22 -0
  240. package/gs/crypto/rand/index.test.ts +89 -1
  241. package/gs/crypto/rand/index.ts +103 -1
  242. package/gs/crypto/rand/meta.json +4 -1
  243. package/gs/crypto/sha256/index.test.ts +78 -0
  244. package/gs/crypto/sha256/index.ts +150 -0
  245. package/gs/crypto/sha256/meta.json +9 -0
  246. package/gs/crypto/sha512/index.test.ts +31 -0
  247. package/gs/crypto/sha512/index.ts +161 -0
  248. package/gs/crypto/sha512/meta.json +11 -0
  249. package/gs/encoding/json/index.test.ts +25 -3
  250. package/gs/encoding/json/index.ts +21 -3
  251. package/gs/errors/errors.test.ts +4 -1
  252. package/gs/errors/errors.ts +32 -8
  253. package/gs/fmt/fmt.test.ts +3 -1
  254. package/gs/fmt/fmt.ts +1 -5
  255. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +62 -7
  256. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +78 -36
  257. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +32 -11
  258. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +122 -43
  259. package/gs/github.com/aperturerobotics/starpc/srpc/index.test.ts +31 -0
  260. package/gs/github.com/aperturerobotics/starpc/srpc/index.ts +518 -4
  261. package/gs/github.com/aperturerobotics/starpc/srpc/meta.json +6 -0
  262. package/gs/github.com/aperturerobotics/util/conc/index.test.ts +30 -0
  263. package/gs/github.com/aperturerobotics/util/conc/index.ts +172 -0
  264. package/gs/github.com/aperturerobotics/util/conc/meta.json +9 -0
  265. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.ts +1 -4
  266. package/gs/github.com/hack-pad/safejs/internal/catch/index.test.ts +35 -0
  267. package/gs/github.com/hack-pad/safejs/internal/catch/index.ts +65 -0
  268. package/gs/github.com/hack-pad/safejs/internal/catch/meta.json +9 -0
  269. package/gs/github.com/klauspost/compress/internal/le/index.test.ts +2 -1
  270. package/gs/github.com/klauspost/compress/internal/le/index.ts +6 -5
  271. package/gs/github.com/mr-tron/base58/base58/index.test.ts +70 -0
  272. package/gs/github.com/mr-tron/base58/base58/index.ts +231 -0
  273. package/gs/github.com/mr-tron/base58/base58/meta.json +3 -0
  274. package/gs/github.com/zeebo/blake3/internal/consts/index.test.ts +46 -0
  275. package/gs/github.com/zeebo/blake3/internal/consts/index.ts +26 -0
  276. package/gs/go/token/index.ts +17 -4
  277. package/gs/hash/fnv/index.test.ts +67 -0
  278. package/gs/hash/fnv/index.ts +351 -0
  279. package/gs/hash/fnv/meta.json +3 -0
  280. package/gs/hash/index.test.ts +37 -0
  281. package/gs/hash/index.ts +118 -0
  282. package/gs/hash/meta.json +5 -0
  283. package/gs/internal/byteorder/index.test.ts +6 -6
  284. package/gs/io/fs/readlink.ts +40 -48
  285. package/gs/io/fs/walk.ts +10 -2
  286. package/gs/io/io.ts +4 -1
  287. package/gs/iter/iter.ts +8 -2
  288. package/gs/maps/iter.ts +69 -26
  289. package/gs/maps/maps.test.ts +23 -0
  290. package/gs/maps/maps.ts +6 -6
  291. package/gs/math/bits/index.test.ts +20 -0
  292. package/gs/math/bits/index.ts +15 -28
  293. package/gs/mime/index.ts +8 -2
  294. package/gs/net/http/httptest/index.test.ts +53 -0
  295. package/gs/net/http/httptest/index.ts +98 -3
  296. package/gs/net/http/index.test.ts +129 -1
  297. package/gs/net/http/index.ts +370 -19
  298. package/gs/net/http/meta.json +6 -0
  299. package/gs/net/http/pprof/index.test.ts +47 -0
  300. package/gs/net/http/pprof/index.ts +65 -0
  301. package/gs/os/error.gs.ts +9 -10
  302. package/gs/os/error.test.ts +41 -0
  303. package/gs/os/file_unix_js.test.ts +55 -0
  304. package/gs/os/tempfile.gs.test.ts +37 -10
  305. package/gs/os/types_js.gs.ts +94 -15
  306. package/gs/path/filepath/match.ts +4 -1
  307. package/gs/path/filepath/meta.json +6 -0
  308. package/gs/path/filepath/path.test.ts +57 -2
  309. package/gs/path/filepath/path.ts +91 -12
  310. package/gs/reflect/field.test.ts +63 -0
  311. package/gs/reflect/index.ts +4 -1
  312. package/gs/reflect/iter.ts +2 -2
  313. package/gs/reflect/map.test.ts +24 -2
  314. package/gs/reflect/map.ts +35 -0
  315. package/gs/reflect/type.ts +543 -60
  316. package/gs/reflect/typefor.test.ts +100 -0
  317. package/gs/reflect/types.ts +3 -1
  318. package/gs/reflect/value.ts +50 -1
  319. package/gs/reflect/visiblefields.ts +1 -1
  320. package/gs/runtime/debug/index.test.ts +22 -1
  321. package/gs/runtime/debug/index.ts +88 -0
  322. package/gs/runtime/pprof/index.test.ts +36 -0
  323. package/gs/runtime/pprof/index.ts +104 -0
  324. package/gs/runtime/pprof/meta.json +6 -0
  325. package/gs/runtime/trace/index.test.ts +45 -0
  326. package/gs/runtime/trace/index.ts +97 -0
  327. package/gs/runtime/trace/meta.json +7 -0
  328. package/gs/slices/meta.json +2 -1
  329. package/gs/slices/slices.test.ts +86 -0
  330. package/gs/slices/slices.ts +284 -37
  331. package/gs/sort/slice.gs.ts +73 -23
  332. package/gs/sort/slice.test.ts +40 -0
  333. package/gs/strings/builder.test.ts +8 -0
  334. package/gs/strings/builder.ts +29 -17
  335. package/gs/strings/iter.test.ts +5 -7
  336. package/gs/strings/iter.ts +146 -71
  337. package/gs/strings/replace.test.ts +1 -4
  338. package/gs/strings/replace.ts +6 -6
  339. package/gs/strings/strings.test.ts +4 -0
  340. package/gs/strings/strings.ts +54 -6
  341. package/gs/sync/sync.test.ts +57 -1
  342. package/gs/sync/sync.ts +45 -13
  343. package/gs/syscall/errors.ts +158 -115
  344. package/gs/syscall/fs.ts +8 -8
  345. package/gs/syscall/js/index.ts +49 -22
  346. package/gs/syscall/net.test.ts +26 -0
  347. package/gs/syscall/types.ts +7 -2
  348. package/gs/testing/testing.test.ts +56 -0
  349. package/gs/testing/testing.ts +27 -10
  350. package/gs/time/meta.json +2 -2
  351. package/gs/time/time.test.ts +4 -0
  352. package/gs/time/time.ts +33 -2
  353. package/gs/unicode/unicode.test.ts +14 -3
  354. package/gs/unicode/unicode.ts +1 -5
  355. package/gs/unique/index.ts +9 -2
  356. package/package.json +3 -3
@@ -235,6 +235,44 @@ func TestCompilePackagesLazilyInitializesCrossFilePackageVars(t *testing.T) {
235
235
  }
236
236
  }
237
237
 
238
+ func TestCompilePackagesLazilyInitializesSameFileLaterPackageVars(t *testing.T) {
239
+ moduleDir := writePackageGraphFixture(t, map[string]string{
240
+ "go.mod": "module example.test/lazylatervars\n\ngo 1.25.3\n",
241
+ "main.go": strings.Join([]string{
242
+ "package main",
243
+ "type detail struct { n int }",
244
+ "var table = []detail{{n: later.n}}",
245
+ "var later = detail{n: 7}",
246
+ "func main() { println(table[0].n) }",
247
+ "",
248
+ }, "\n"),
249
+ })
250
+ outputDir := filepath.Join(t.TempDir(), "output")
251
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
252
+ if err != nil {
253
+ t.Fatal(err.Error())
254
+ }
255
+
256
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
257
+ t.Fatal(err.Error())
258
+ }
259
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "lazylatervars", "main.gs.ts")
260
+ content, err := os.ReadFile(outputFile)
261
+ if err != nil {
262
+ t.Fatal(err.Error())
263
+ }
264
+ text := string(content)
265
+ for _, want := range []string{
266
+ "export let table: $.Slice<detail> = undefined as unknown as $.Slice<detail>",
267
+ "export function __goscript_get_table(): $.Slice<detail>",
268
+ "export let later: detail = $.markAsStructValue(new detail({n: 7}))",
269
+ } {
270
+ if !strings.Contains(text, want) {
271
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
272
+ }
273
+ }
274
+ }
275
+
238
276
  func TestCompilePackagesLazilyInitializesFunctionBodyPackageVarDependencies(t *testing.T) {
239
277
  moduleDir := writePackageGraphFixture(t, map[string]string{
240
278
  "go.mod": "module example.test/lazybodyvars\n\ngo 1.25.3\n",
@@ -278,17 +316,22 @@ func TestCompilePackagesLazilyInitializesFunctionBodyPackageVarDependencies(t *t
278
316
  }
279
317
  }
280
318
 
281
- func TestCompilePackagesEmitsShadowedBuiltinCalls(t *testing.T) {
319
+ func TestCompilePackagesInitializesLazyAsyncPackageVarsBeforeInit(t *testing.T) {
282
320
  moduleDir := writePackageGraphFixture(t, map[string]string{
283
- "go.mod": "module example.test/shadowbuiltin\n\ngo 1.25.3\n",
321
+ "go.mod": "module example.test/lazyasyncvars\n\ngo 1.25.3\n",
284
322
  "main.go": strings.Join([]string{
285
- "package shadowbuiltin",
286
- "type Value struct {",
287
- " N int",
288
- "}",
289
- "func Build(new func() (*Value, error)) (*Value, error) {",
290
- " return new()",
323
+ "package main",
324
+ "import \"sync\"",
325
+ "var lock sync.Mutex",
326
+ "var first = makeFirst()",
327
+ "func makeFirst() int {",
328
+ " lock.Lock()",
329
+ " defer lock.Unlock()",
330
+ " return later",
291
331
  "}",
332
+ "var later = 7",
333
+ "func init() { println(first) }",
334
+ "func main() {}",
292
335
  "",
293
336
  }, "\n"),
294
337
  })
@@ -301,25 +344,36 @@ func TestCompilePackagesEmitsShadowedBuiltinCalls(t *testing.T) {
301
344
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
302
345
  t.Fatal(err.Error())
303
346
  }
304
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "shadowbuiltin", "main.gs.ts")
347
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "lazyasyncvars", "main.gs.ts")
305
348
  content, err := os.ReadFile(outputFile)
306
349
  if err != nil {
307
350
  t.Fatal(err.Error())
308
351
  }
309
352
  text := string(content)
310
- if !strings.Contains(text, "return await _new!()") {
311
- t.Fatalf("shadowed builtin call was not emitted as a callable value:\n%s", text)
353
+ for _, want := range []string{
354
+ "async function __goscript_init_first(): globalThis.Promise<void>",
355
+ "first = await makeFirst()",
356
+ "export function __goscript_get_first(): number",
357
+ "await __goscript_init_first()",
358
+ "__goscriptInit0()",
359
+ } {
360
+ if !strings.Contains(text, want) {
361
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
362
+ }
312
363
  }
313
364
  }
314
365
 
315
- func TestCompilePackagesQuotesRawStringLiterals(t *testing.T) {
366
+ func TestCompilePackagesAssignsLazyPackageVarsDirectly(t *testing.T) {
316
367
  moduleDir := writePackageGraphFixture(t, map[string]string{
317
- "go.mod": "module example.test/rawstrings\n\ngo 1.25.3\n",
368
+ "go.mod": "module example.test/lazyassign\n\ngo 1.25.3\n",
318
369
  "main.go": strings.Join([]string{
319
- "package rawstrings",
320
- "func Values() (string, string) {",
321
- " return `\\u00`, `invalid escape char after \\`",
370
+ "package main",
371
+ "var table = []int{later}",
372
+ "var later = 1",
373
+ "func init() {",
374
+ " table = append(table, 2)",
322
375
  "}",
376
+ "func main() { println(len(table)) }",
323
377
  "",
324
378
  }, "\n"),
325
379
  })
@@ -332,174 +386,163 @@ func TestCompilePackagesQuotesRawStringLiterals(t *testing.T) {
332
386
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
333
387
  t.Fatal(err.Error())
334
388
  }
335
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "rawstrings", "main.gs.ts")
389
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "lazyassign", "main.gs.ts")
336
390
  content, err := os.ReadFile(outputFile)
337
391
  if err != nil {
338
392
  t.Fatal(err.Error())
339
393
  }
340
394
  text := string(content)
341
- if !strings.Contains(text, `return ["\\u00", "invalid escape char after \\"]`) {
342
- t.Fatalf("raw string literals were not quoted for TypeScript:\n%s", text)
395
+ if !strings.Contains(text, "table = $.append(__goscript_get_table(), 2)") {
396
+ t.Fatalf("missing direct lazy package var assignment:\n%s", text)
397
+ }
398
+ if strings.Contains(text, "__goscript_get_table() =") {
399
+ t.Fatalf("lazy getter used as assignment target:\n%s", text)
343
400
  }
344
401
  }
345
402
 
346
- func TestCompilePackagesUsesEmbedOverride(t *testing.T) {
403
+ func TestCompilePackagesAssignsImportedPackageVarsThroughSetters(t *testing.T) {
347
404
  moduleDir := writePackageGraphFixture(t, map[string]string{
348
- "go.mod": "module example.test/embedblank\n\ngo 1.25.3\n",
349
- "version.txt": "1.2.3\n",
350
- "version..txt": "4.5.6\n",
351
- "binary.bin": string([]byte{0x00, 0xff, 0x80, 0x41}),
405
+ "go.mod": "module example.test/pkgvarassign\n\ngo 1.25.3\n",
406
+ "dep/dep.go": strings.Join([]string{
407
+ "package dep",
408
+ "var Count int",
409
+ "",
410
+ }, "\n"),
352
411
  "main.go": strings.Join([]string{
353
- "package embedblank",
354
- "import _ \"embed\"",
355
- "//go:embed version.txt",
356
- "var Version string",
357
- "//go:embed version..txt",
358
- "var Dotted string",
359
- "//go:embed binary.bin",
360
- "var Binary []byte",
361
- "func GetVersion() string {",
362
- " return Version",
412
+ "package main",
413
+ "import \"example.test/pkgvarassign/dep\"",
414
+ "func bump() {",
415
+ " dep.Count = 1",
416
+ " dep.Count += 16",
417
+ " dep.Count++",
363
418
  "}",
364
419
  "",
365
420
  }, "\n"),
366
421
  })
367
422
  outputDir := filepath.Join(t.TempDir(), "output")
368
- comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir, AllDependencies: true}, nil, nil)
423
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
369
424
  if err != nil {
370
425
  t.Fatal(err.Error())
371
426
  }
372
427
 
373
- result, err := comp.CompilePackages(context.Background(), ".")
374
- if err != nil {
428
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
375
429
  t.Fatal(err.Error())
376
430
  }
377
- if !slices.Contains(result.CopiedPackages, "embed") {
378
- t.Fatalf("embed override was not copied: %#v", result.CopiedPackages)
379
- }
380
- if _, err := os.Stat(filepath.Join(outputDir, "@goscript", "embed", "index.ts")); err != nil {
381
- t.Fatalf("embed override missing from output: %v", err)
382
- }
383
- if _, err := os.Stat(filepath.Join(outputDir, "@goscript", "embed", "embed.gs.ts")); !os.IsNotExist(err) {
384
- t.Fatalf("stdlib embed was emitted instead of override: %v", err)
385
- }
386
- content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "embedblank", "main.gs.ts"))
431
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "pkgvarassign", "main.gs.ts")
432
+ content, err := os.ReadFile(outputFile)
387
433
  if err != nil {
388
434
  t.Fatal(err.Error())
389
435
  }
390
- if !strings.Contains(string(content), "export let Version: string = \"1.2.3\\n\"") {
391
- t.Fatalf("embedded string content was not emitted:\n%s", string(content))
392
- }
393
- if !strings.Contains(string(content), "export let Dotted: string = \"4.5.6\\n\"") {
394
- t.Fatalf("embedded dotted filename content was not emitted:\n%s", string(content))
436
+ text := string(content)
437
+ for _, want := range []string{
438
+ "dep.__goscript_set_Count(1)",
439
+ "dep.__goscript_set_Count(dep.Count + 16)",
440
+ "dep.__goscript_set_Count(dep.Count + 1)",
441
+ } {
442
+ if !strings.Contains(text, want) {
443
+ t.Fatalf("missing imported package var setter assignment %q:\n%s", want, text)
444
+ }
395
445
  }
396
- if !strings.Contains(string(content), "export let Binary: $.Slice<number> = new Uint8Array([0, 255, 128, 65])") {
397
- t.Fatalf("embedded binary content was not emitted as byte values:\n%s", string(content))
446
+ if strings.Contains(text, "dep.Count +=") || strings.Contains(text, "dep.Count++") {
447
+ t.Fatalf("imported package var assigned directly:\n%s", text)
398
448
  }
399
449
  }
400
450
 
401
- func TestCompilePackagesEmitsPackageLocalImport(t *testing.T) {
451
+ func TestCompilePackagesAliasesForInitShortDeclShadow(t *testing.T) {
402
452
  moduleDir := writePackageGraphFixture(t, map[string]string{
403
- "go.mod": "module example.test/imports\n\ngo 1.25.3\n",
453
+ "go.mod": "module example.test/forinitshadow\n\ngo 1.25.3\n",
404
454
  "main.go": strings.Join([]string{
405
455
  "package main",
406
- "import \"example.test/imports/subpkg\"",
407
- "func main() {",
408
- " var b subpkg.Builder",
409
- " b.Set(\"built\")",
410
- " println(b.Value)",
411
- " println(subpkg.Greet(\"world\"))",
412
- " println(localMessage())",
413
- "}",
414
- "",
415
- }, "\n"),
416
- "helper.go": strings.Join([]string{
417
- "package main",
418
- "func localMessage() string {",
419
- " return \"from helper\"",
420
- "}",
421
- "",
422
- }, "\n"),
423
- "subpkg/subpkg.go": strings.Join([]string{
424
- "package subpkg",
425
- "type Builder struct {",
426
- " Value string",
427
- "}",
428
- "func (b *Builder) Set(value string) {",
429
- " b.Value = value",
430
- "}",
431
- "func Greet(name string) string {",
432
- " return \"Hello, \" + name",
456
+ "func checksum(n int) int {",
457
+ " total := 0",
458
+ " for n := n - 4; total <= n; total++ {",
459
+ " total += n",
460
+ " }",
461
+ " return total + n",
433
462
  "}",
434
463
  "",
435
464
  }, "\n"),
436
465
  })
437
466
  outputDir := filepath.Join(t.TempDir(), "output")
438
- comp, err := NewCompiler(&Config{
439
- Dir: moduleDir,
440
- OutputPath: outputDir,
441
- AllDependencies: true,
442
- }, nil, nil)
467
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
443
468
  if err != nil {
444
469
  t.Fatal(err.Error())
445
470
  }
446
471
 
447
- result, err := comp.CompilePackages(context.Background(), ".")
448
- if err != nil {
472
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
449
473
  t.Fatal(err.Error())
450
474
  }
451
- if result == nil || len(result.CompiledPackages) != 2 {
452
- t.Fatalf("unexpected result: %#v", result)
453
- }
454
- mainFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "main.gs.ts")
455
- mainContent, err := os.ReadFile(mainFile)
475
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "forinitshadow", "main.gs.ts")
476
+ content, err := os.ReadFile(outputFile)
456
477
  if err != nil {
457
478
  t.Fatal(err.Error())
458
479
  }
459
- if !strings.Contains(string(mainContent), "import * as subpkg from \"@goscript/example.test/imports/subpkg/index.js\"") {
460
- t.Fatalf("missing package-local import:\n%s", string(mainContent))
480
+ text := string(content)
481
+ if strings.Contains(text, "for (let n = n - 4") {
482
+ t.Fatalf("for init short declaration references itself:\n%s", text)
461
483
  }
462
- if !strings.Contains(string(mainContent), "let b: $.VarRef<subpkg.Builder> = $.varRef($.markAsStructValue(new subpkg.Builder()))") {
463
- t.Fatalf("missing imported struct zero value qualification:\n%s", string(mainContent))
484
+ if !regexp.MustCompile(`let __goscriptShadow\d+ = n\s+for \(let __goscriptShadow\d+ = __goscriptShadow\d+ - 4; total <= __goscriptShadow\d+; total\+\+\)`).MatchString(text) {
485
+ t.Fatalf("missing aliased for init shadow declaration:\n%s", text)
464
486
  }
465
- if !strings.Contains(string(mainContent), "import * as __goscript_helper from \"./helper.gs.ts\"") ||
466
- !strings.Contains(string(mainContent), "$.println(__goscript_helper.localMessage())") {
467
- t.Fatalf("missing same-package helper import:\n%s", string(mainContent))
487
+ if !strings.Contains(text, "return total + n") {
488
+ t.Fatalf("outer n was not preserved after the loop:\n%s", text)
468
489
  }
469
- indexFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "subpkg", "index.ts")
470
- indexContent, err := os.ReadFile(indexFile)
490
+ }
491
+
492
+ func TestCompilePackagesReadsShadowedVarRefStructFieldsOnce(t *testing.T) {
493
+ moduleDir := writePackageGraphFixture(t, map[string]string{
494
+ "go.mod": "module example.test/shadowvarreffield\n\ngo 1.25.3\n",
495
+ "main.go": strings.Join([]string{
496
+ "package main",
497
+ "type key struct { pad []byte }",
498
+ "func fill(k *key) error { return nil }",
499
+ "func size() int {",
500
+ " var key key",
501
+ " if err := fill(&key); err != nil {",
502
+ " return 0",
503
+ " }",
504
+ " return len(key.pad)",
505
+ "}",
506
+ "func main() { println(size()) }",
507
+ "",
508
+ }, "\n"),
509
+ })
510
+ outputDir := filepath.Join(t.TempDir(), "output")
511
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
471
512
  if err != nil {
472
513
  t.Fatal(err.Error())
473
514
  }
474
- if string(indexContent) != "export { Builder, Greet } from \"./subpkg.gs.ts\"\n" {
475
- t.Fatalf("unexpected subpkg index:\n%s", string(indexContent))
515
+
516
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
517
+ t.Fatal(err.Error())
476
518
  }
477
- mainIndexFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "index.ts")
478
- mainIndexContent, err := os.ReadFile(mainIndexFile)
519
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "shadowvarreffield", "main.gs.ts")
520
+ content, err := os.ReadFile(outputFile)
479
521
  if err != nil {
480
522
  t.Fatal(err.Error())
481
523
  }
482
- if strings.Contains(string(mainIndexContent), "localMessage") {
483
- t.Fatalf("unexported helper leaked into package index:\n%s", string(mainIndexContent))
524
+ text := string(content)
525
+ if !strings.Contains(text, "$.len(__goscriptShadow0.value.pad)") {
526
+ t.Fatalf("missing single dereference field read:\n%s", text)
527
+ }
528
+ if strings.Contains(text, ".value.value.pad") {
529
+ t.Fatalf("shadowed VarRef struct field was dereferenced twice:\n%s", text)
484
530
  }
485
531
  }
486
532
 
487
- func TestCompilePackagesEmitsIndexAddressRefs(t *testing.T) {
533
+ func TestCompilePackagesWrapsChannelSendInterfaceValues(t *testing.T) {
488
534
  moduleDir := writePackageGraphFixture(t, map[string]string{
489
- "go.mod": "module example.test/indexaddr\n\ngo 1.25.3\n",
535
+ "go.mod": "module example.test/chansendiface\n\ngo 1.25.3\n",
490
536
  "main.go": strings.Join([]string{
491
537
  "package main",
492
- "type Item struct { N int }",
493
- "func set(ptr *int, value int) {",
494
- " *ptr = value",
495
- "}",
496
- "func Use(values []int, i int) int {",
497
- " set(&values[i], 9)",
498
- " return values[i]",
499
- "}",
500
- "func Items() []*Item {",
501
- " return []*Item{{N: 1}}",
538
+ "type item interface { Name() string }",
539
+ "type concrete struct{}",
540
+ "func (*concrete) Name() string { return \"ok\" }",
541
+ "func send(ch chan item) {",
542
+ " v := &concrete{}",
543
+ " ch <- v",
502
544
  "}",
545
+ "func main() {}",
503
546
  "",
504
547
  }, "\n"),
505
548
  })
@@ -509,53 +552,33 @@ func TestCompilePackagesEmitsIndexAddressRefs(t *testing.T) {
509
552
  t.Fatal(err.Error())
510
553
  }
511
554
 
512
- _, err = comp.CompilePackages(context.Background(), ".")
513
- if err != nil {
555
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
514
556
  t.Fatal(err.Error())
515
557
  }
516
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "indexaddr", "main.gs.ts")
558
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "chansendiface", "main.gs.ts")
517
559
  content, err := os.ReadFile(outputFile)
518
560
  if err != nil {
519
561
  t.Fatal(err.Error())
520
562
  }
521
563
  text := string(content)
522
- if !strings.Contains(text, "set($.indexRef(values!, i), 9)") {
523
- t.Fatalf("missing index address reference:\n%s", text)
524
- }
525
- if !strings.Contains(text, "new Item({N: 1})") {
526
- t.Fatalf("missing elided pointer composite literal:\n%s", text)
564
+ if !strings.Contains(text, "$.chanSend(ch, $.interfaceValue<item | null>(v, \"*main.concrete\"))") {
565
+ t.Fatalf("missing interface wrapper for channel send:\n%s", text)
527
566
  }
528
567
  }
529
568
 
530
- func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
569
+ func TestCompilePackagesBindsFuncLiteralVarRefParams(t *testing.T) {
531
570
  moduleDir := writePackageGraphFixture(t, map[string]string{
532
- "go.mod": "module example.test/structs\n\ngo 1.25.3\n",
571
+ "go.mod": "module example.test/funclitvarref\n\ngo 1.25.3\n",
533
572
  "main.go": strings.Join([]string{
534
573
  "package main",
535
- "type Counter struct {",
536
- " // Value counts reads.",
537
- " Value int `json:\"value\"`",
538
- "}",
539
- "func (c Counter) Read() int {",
540
- " return c.Value",
541
- "}",
542
- "func (c *Counter) Set(v int) {",
543
- " c.Value = v",
544
- "}",
545
- "func NewCounter() *Counter {",
546
- " return &Counter{Value: 3}",
547
- "}",
574
+ "type words []int",
575
+ "func (w *words) Add(v int) { *w = append(*w, v) }",
548
576
  "func main() {",
549
- " original := Counter{Value: 1}",
550
- "",
551
- " // Copy should stay readable in generated output.",
552
- " copy := original",
553
- " pointer := &original",
554
- " pointer.Set(2)",
555
- " NewCounter().Set(5)",
556
- " var iface any = pointer",
557
- " _, ok := iface.(*Counter)",
558
- " println(copy.Read(), original.Read(), ok)",
577
+ " addLen := func(w words) int {",
578
+ " w.Add(7)",
579
+ " return len(w)",
580
+ " }",
581
+ " println(addLen(nil))",
559
582
  "}",
560
583
  "",
561
584
  }, "\n"),
@@ -569,26 +592,16 @@ func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
569
592
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
570
593
  t.Fatal(err.Error())
571
594
  }
572
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "structs", "main.gs.ts")
595
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "funclitvarref", "main.gs.ts")
573
596
  content, err := os.ReadFile(outputFile)
574
597
  if err != nil {
575
598
  t.Fatal(err.Error())
576
599
  }
577
600
  text := string(content)
578
601
  for _, want := range []string{
579
- "export class Counter",
580
- "// Value counts reads.\n\tpublic get Value(): number",
581
- "public clone(): Counter",
582
- "public Read(): number",
583
- "public Set(v: number): void",
584
- "let original = $.varRef($.markAsStructValue(new Counter({Value: 1})))",
585
- "let original = $.varRef($.markAsStructValue(new Counter({Value: 1})))\n\n\t// Copy should stay readable in generated output.\n\tlet copy",
586
- "let copy = $.markAsStructValue($.cloneStructValue(original.value))",
587
- "let pointer: Counter | $.VarRef<Counter> | null = original",
588
- "$.pointerValue<Counter>(pointer).Set(2)",
589
- "$.pointerValue<Counter>(NewCounter()).Set(5)",
590
- "let [, ok] = $.typeAssertTuple<Counter | $.VarRef<Counter> | null>(iface, { kind: $.TypeKind.Pointer, elemType: \"main.Counter\" })",
591
- "\"Value\": { type: { kind: $.TypeKind.Basic, name: \"int\" }, tag: \"json:\\\"value\\\"\" }",
602
+ "(__goscriptParam0: words): number => {",
603
+ "let w: $.VarRef<words> = $.varRef(__goscriptParam0)",
604
+ "words_Add(w, 7)",
592
605
  } {
593
606
  if !strings.Contains(text, want) {
594
607
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -596,18 +609,19 @@ func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
596
609
  }
597
610
  }
598
611
 
599
- func TestCompilePackagesEmitsNestedPointerStorageAssertions(t *testing.T) {
612
+ func TestCompilePackagesAnnotatesNewPointerShortDecls(t *testing.T) {
600
613
  moduleDir := writePackageGraphFixture(t, map[string]string{
601
- "go.mod": "module example.test/pointers\n\ngo 1.25.3\n",
614
+ "go.mod": "module example.test/newptrdecl\n\ngo 1.25.3\n",
602
615
  "main.go": strings.Join([]string{
603
616
  "package main",
617
+ "type OID []int",
618
+ "func use(*OID) {}",
604
619
  "func main() {",
605
- " var x int = 10",
606
- " p1 := &x",
607
- " p2 := &p1",
608
- " p3 := &p2",
609
- " ***p3 = 12",
610
- " println(x)",
620
+ " oid := new(OID)",
621
+ " if len(*oid) == 0 {",
622
+ " oid = nil",
623
+ " }",
624
+ " use(oid)",
611
625
  "}",
612
626
  "",
613
627
  }, "\n"),
@@ -621,18 +635,591 @@ func TestCompilePackagesEmitsNestedPointerStorageAssertions(t *testing.T) {
621
635
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
622
636
  t.Fatal(err.Error())
623
637
  }
624
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "pointers", "main.gs.ts")
638
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "newptrdecl", "main.gs.ts")
625
639
  content, err := os.ReadFile(outputFile)
626
640
  if err != nil {
627
641
  t.Fatal(err.Error())
628
642
  }
629
643
  text := string(content)
630
644
  for _, want := range []string{
631
- "let x: $.VarRef<number> = $.varRef(10)",
632
- "let p1 = $.varRef(x)",
633
- "let p2 = $.varRef(p1)",
634
- "let p3 = p2",
635
- "$.pointerValue<$.VarRef<number> | null>($.pointerValue<$.VarRef<$.VarRef<number> | null> | null>(p3))!.value = 12",
645
+ "let oid: $.VarRef<OID> | null = $.varRef<OID>(null as OID)",
646
+ "oid = null",
647
+ "use(oid)",
648
+ } {
649
+ if !strings.Contains(text, want) {
650
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
651
+ }
652
+ }
653
+ }
654
+
655
+ func TestCompilePackagesEmitsShadowedBuiltinCalls(t *testing.T) {
656
+ moduleDir := writePackageGraphFixture(t, map[string]string{
657
+ "go.mod": "module example.test/shadowbuiltin\n\ngo 1.25.3\n",
658
+ "main.go": strings.Join([]string{
659
+ "package shadowbuiltin",
660
+ "type Value struct {",
661
+ " N int",
662
+ "}",
663
+ "func Build(new func() (*Value, error)) (*Value, error) {",
664
+ " return new()",
665
+ "}",
666
+ "",
667
+ }, "\n"),
668
+ })
669
+ outputDir := filepath.Join(t.TempDir(), "output")
670
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
671
+ if err != nil {
672
+ t.Fatal(err.Error())
673
+ }
674
+
675
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
676
+ t.Fatal(err.Error())
677
+ }
678
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "shadowbuiltin", "main.gs.ts")
679
+ content, err := os.ReadFile(outputFile)
680
+ if err != nil {
681
+ t.Fatal(err.Error())
682
+ }
683
+ text := string(content)
684
+ if !strings.Contains(text, "return await _new!()") {
685
+ t.Fatalf("shadowed builtin call was not emitted as a callable value:\n%s", text)
686
+ }
687
+ }
688
+
689
+ func TestCompilePackagesQuotesRawStringLiterals(t *testing.T) {
690
+ moduleDir := writePackageGraphFixture(t, map[string]string{
691
+ "go.mod": "module example.test/rawstrings\n\ngo 1.25.3\n",
692
+ "main.go": strings.Join([]string{
693
+ "package rawstrings",
694
+ "func Values() (string, string) {",
695
+ " return `\\u00`, `invalid escape char after \\`",
696
+ "}",
697
+ "",
698
+ }, "\n"),
699
+ })
700
+ outputDir := filepath.Join(t.TempDir(), "output")
701
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
702
+ if err != nil {
703
+ t.Fatal(err.Error())
704
+ }
705
+
706
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
707
+ t.Fatal(err.Error())
708
+ }
709
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "rawstrings", "main.gs.ts")
710
+ content, err := os.ReadFile(outputFile)
711
+ if err != nil {
712
+ t.Fatal(err.Error())
713
+ }
714
+ text := string(content)
715
+ if !strings.Contains(text, `return ["\\u00", "invalid escape char after \\"]`) {
716
+ t.Fatalf("raw string literals were not quoted for TypeScript:\n%s", text)
717
+ }
718
+ }
719
+
720
+ func TestCompilePackagesEmitsBinaryStringLiterals(t *testing.T) {
721
+ moduleDir := writePackageGraphFixture(t, map[string]string{
722
+ "go.mod": "module example.test/binarystrings\n\ngo 1.25.3\n",
723
+ "main.go": strings.Join([]string{
724
+ "package binarystrings",
725
+ "const DecodeMap = \"\\xff\\x80A\"",
726
+ "func Value() string {",
727
+ " return DecodeMap",
728
+ "}",
729
+ "",
730
+ }, "\n"),
731
+ })
732
+ outputDir := filepath.Join(t.TempDir(), "output")
733
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
734
+ if err != nil {
735
+ t.Fatal(err.Error())
736
+ }
737
+
738
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
739
+ t.Fatal(err.Error())
740
+ }
741
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "binarystrings", "main.gs.ts")
742
+ content, err := os.ReadFile(outputFile)
743
+ if err != nil {
744
+ t.Fatal(err.Error())
745
+ }
746
+ if !strings.Contains(string(content), "$.bytesToString(new Uint8Array([255, 128, 65]))") {
747
+ t.Fatalf("binary string literal was not emitted as byte-backed string:\n%s", string(content))
748
+ }
749
+ }
750
+
751
+ func TestCompilePackagesUsesEmbedOverride(t *testing.T) {
752
+ moduleDir := writePackageGraphFixture(t, map[string]string{
753
+ "go.mod": "module example.test/embedblank\n\ngo 1.25.3\n",
754
+ "version.txt": "1.2.3\n",
755
+ "version..txt": "4.5.6\n",
756
+ "binary.bin": string([]byte{0x00, 0xff, 0x80, 0x41}),
757
+ "main.go": strings.Join([]string{
758
+ "package embedblank",
759
+ "import _ \"embed\"",
760
+ "//go:embed version.txt",
761
+ "var Version string",
762
+ "//go:embed version..txt",
763
+ "var Dotted string",
764
+ "//go:embed binary.bin",
765
+ "var Binary []byte",
766
+ "func GetVersion() string {",
767
+ " return Version",
768
+ "}",
769
+ "",
770
+ }, "\n"),
771
+ })
772
+ outputDir := filepath.Join(t.TempDir(), "output")
773
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir, AllDependencies: true}, nil, nil)
774
+ if err != nil {
775
+ t.Fatal(err.Error())
776
+ }
777
+
778
+ result, err := comp.CompilePackages(context.Background(), ".")
779
+ if err != nil {
780
+ t.Fatal(err.Error())
781
+ }
782
+ if !slices.Contains(result.CopiedPackages, "embed") {
783
+ t.Fatalf("embed override was not copied: %#v", result.CopiedPackages)
784
+ }
785
+ if _, err := os.Stat(filepath.Join(outputDir, "@goscript", "embed", "index.ts")); err != nil {
786
+ t.Fatalf("embed override missing from output: %v", err)
787
+ }
788
+ if _, err := os.Stat(filepath.Join(outputDir, "@goscript", "embed", "embed.gs.ts")); !os.IsNotExist(err) {
789
+ t.Fatalf("stdlib embed was emitted instead of override: %v", err)
790
+ }
791
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "embedblank", "main.gs.ts"))
792
+ if err != nil {
793
+ t.Fatal(err.Error())
794
+ }
795
+ if !strings.Contains(string(content), "export let Version: string = \"1.2.3\\n\"") {
796
+ t.Fatalf("embedded string content was not emitted:\n%s", string(content))
797
+ }
798
+ if !strings.Contains(string(content), "export let Dotted: string = \"4.5.6\\n\"") {
799
+ t.Fatalf("embedded dotted filename content was not emitted:\n%s", string(content))
800
+ }
801
+ if !strings.Contains(string(content), "export let Binary: $.Slice<number> = new Uint8Array([0, 255, 128, 65])") {
802
+ t.Fatalf("embedded binary content was not emitted as byte values:\n%s", string(content))
803
+ }
804
+ }
805
+
806
+ func TestCompilePackagesEmitsPackageLocalImport(t *testing.T) {
807
+ moduleDir := writePackageGraphFixture(t, map[string]string{
808
+ "go.mod": "module example.test/imports\n\ngo 1.25.3\n",
809
+ "main.go": strings.Join([]string{
810
+ "package main",
811
+ "import \"example.test/imports/subpkg\"",
812
+ "func main() {",
813
+ " var b subpkg.Builder",
814
+ " b.Set(\"built\")",
815
+ " println(b.Value)",
816
+ " println(subpkg.Greet(\"world\"))",
817
+ " println(localMessage())",
818
+ "}",
819
+ "",
820
+ }, "\n"),
821
+ "helper.go": strings.Join([]string{
822
+ "package main",
823
+ "func localMessage() string {",
824
+ " return \"from helper\"",
825
+ "}",
826
+ "",
827
+ }, "\n"),
828
+ "subpkg/subpkg.go": strings.Join([]string{
829
+ "package subpkg",
830
+ "type Builder struct {",
831
+ " Value string",
832
+ "}",
833
+ "func (b *Builder) Set(value string) {",
834
+ " b.Value = value",
835
+ "}",
836
+ "func Greet(name string) string {",
837
+ " return \"Hello, \" + name",
838
+ "}",
839
+ "",
840
+ }, "\n"),
841
+ })
842
+ outputDir := filepath.Join(t.TempDir(), "output")
843
+ comp, err := NewCompiler(&Config{
844
+ Dir: moduleDir,
845
+ OutputPath: outputDir,
846
+ AllDependencies: true,
847
+ }, nil, nil)
848
+ if err != nil {
849
+ t.Fatal(err.Error())
850
+ }
851
+
852
+ result, err := comp.CompilePackages(context.Background(), ".")
853
+ if err != nil {
854
+ t.Fatal(err.Error())
855
+ }
856
+ if result == nil || len(result.CompiledPackages) != 2 {
857
+ t.Fatalf("unexpected result: %#v", result)
858
+ }
859
+ mainFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "main.gs.ts")
860
+ mainContent, err := os.ReadFile(mainFile)
861
+ if err != nil {
862
+ t.Fatal(err.Error())
863
+ }
864
+ if !strings.Contains(string(mainContent), "import * as subpkg from \"@goscript/example.test/imports/subpkg/index.js\"") {
865
+ t.Fatalf("missing package-local import:\n%s", string(mainContent))
866
+ }
867
+ if !strings.Contains(string(mainContent), "let b: $.VarRef<subpkg.Builder> = $.varRef($.markAsStructValue(new subpkg.Builder()))") {
868
+ t.Fatalf("missing imported struct zero value qualification:\n%s", string(mainContent))
869
+ }
870
+ if !strings.Contains(string(mainContent), "import * as __goscript_helper from \"./helper.gs.ts\"") ||
871
+ !strings.Contains(string(mainContent), "$.println(__goscript_helper.localMessage())") {
872
+ t.Fatalf("missing same-package helper import:\n%s", string(mainContent))
873
+ }
874
+ indexFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "subpkg", "index.ts")
875
+ indexContent, err := os.ReadFile(indexFile)
876
+ if err != nil {
877
+ t.Fatal(err.Error())
878
+ }
879
+ if string(indexContent) != "export { Builder, Greet } from \"./subpkg.gs.ts\"\n" {
880
+ t.Fatalf("unexpected subpkg index:\n%s", string(indexContent))
881
+ }
882
+ mainIndexFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "index.ts")
883
+ mainIndexContent, err := os.ReadFile(mainIndexFile)
884
+ if err != nil {
885
+ t.Fatal(err.Error())
886
+ }
887
+ if strings.Contains(string(mainIndexContent), "localMessage") {
888
+ t.Fatalf("unexported helper leaked into package index:\n%s", string(mainIndexContent))
889
+ }
890
+ }
891
+
892
+ func TestCompilePackagesPreservesSourceImportAliasesForAssociatedMethods(t *testing.T) {
893
+ moduleDir := writePackageGraphFixture(t, map[string]string{
894
+ "go.mod": "module example.test/sourcealiases\n\ngo 1.25.3\n",
895
+ "dep/block/errors.go": strings.Join([]string{
896
+ "package block",
897
+ "import \"errors\"",
898
+ "var ErrUnexpectedType = errors.New(\"unexpected object type\")",
899
+ "",
900
+ }, "\n"),
901
+ "dep/kvtx/block/store.go": strings.Join([]string{
902
+ "package kvtx_block",
903
+ "type KeyValueStore struct{}",
904
+ "",
905
+ }, "\n"),
906
+ "world/world.go": strings.Join([]string{
907
+ "package world",
908
+ "import block \"example.test/sourcealiases/dep/kvtx/block\"",
909
+ "type World struct {",
910
+ " Object *block.KeyValueStore",
911
+ "}",
912
+ "",
913
+ }, "\n"),
914
+ "world/block-world.go": strings.Join([]string{
915
+ "package world",
916
+ "import (",
917
+ " block \"example.test/sourcealiases/dep/block\"",
918
+ " block_kvtx \"example.test/sourcealiases/dep/kvtx/block\"",
919
+ ")",
920
+ "func (w *World) Apply(next any) error {",
921
+ " if _, ok := next.(*block_kvtx.KeyValueStore); !ok {",
922
+ " return block.ErrUnexpectedType",
923
+ " }",
924
+ " return nil",
925
+ "}",
926
+ "",
927
+ }, "\n"),
928
+ })
929
+ outputDir := filepath.Join(t.TempDir(), "output")
930
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
931
+ if err != nil {
932
+ t.Fatal(err.Error())
933
+ }
934
+
935
+ if _, err := comp.CompilePackages(context.Background(), "./world"); err != nil {
936
+ t.Fatal(err.Error())
937
+ }
938
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "sourcealiases", "world", "world.gs.ts")
939
+ content, err := os.ReadFile(outputFile)
940
+ if err != nil {
941
+ t.Fatal(err.Error())
942
+ }
943
+ text := string(content)
944
+ matches := regexp.MustCompile(`import \* as (\w+) from "@goscript/example\.test/sourcealiases/dep/block/index\.js"`).FindStringSubmatch(text)
945
+ if len(matches) != 2 {
946
+ t.Fatalf("missing dep/block import in generated output:\n%s", text)
947
+ }
948
+ if !strings.Contains(text, "return "+matches[1]+".ErrUnexpectedType") {
949
+ t.Fatalf("selector did not use the alias for its source package %q:\n%s", matches[1], text)
950
+ }
951
+ }
952
+
953
+ func TestCompilePackagesEmitsSideEffectImportsForInterfaceRegistry(t *testing.T) {
954
+ moduleDir := writePackageGraphFixture(t, map[string]string{
955
+ "go.mod": "module example.test/interface-registry\n\ngo 1.25.3\n",
956
+ "main.go": strings.Join([]string{
957
+ "package main",
958
+ "import \"example.test/interface-registry/dep\"",
959
+ "type localImpl struct{}",
960
+ "func (*localImpl) Ping() string { return \"pong\" }",
961
+ "func matchLocal(v any) bool {",
962
+ " switch v.(type) {",
963
+ " case Local:",
964
+ " return true",
965
+ " }",
966
+ " return false",
967
+ "}",
968
+ "func matchDep(v any) bool {",
969
+ " _, ok := v.(dep.Remote)",
970
+ " return ok",
971
+ "}",
972
+ "func main() { println(matchLocal(&localImpl{}), matchDep(nil)) }",
973
+ "",
974
+ }, "\n"),
975
+ "local.go": strings.Join([]string{
976
+ "package main",
977
+ "type Local interface { Ping() string }",
978
+ "",
979
+ }, "\n"),
980
+ "dep/dep.go": strings.Join([]string{
981
+ "package dep",
982
+ "type Remote interface { Remote() string }",
983
+ "",
984
+ }, "\n"),
985
+ })
986
+ outputDir := filepath.Join(t.TempDir(), "output")
987
+ comp, err := NewCompiler(&Config{
988
+ Dir: moduleDir,
989
+ OutputPath: outputDir,
990
+ AllDependencies: true,
991
+ }, nil, nil)
992
+ if err != nil {
993
+ t.Fatal(err.Error())
994
+ }
995
+
996
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
997
+ t.Fatal(err.Error())
998
+ }
999
+ mainContent, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "interface-registry", "main.gs.ts"))
1000
+ if err != nil {
1001
+ t.Fatal(err.Error())
1002
+ }
1003
+ mainText := string(mainContent)
1004
+ for _, want := range []string{
1005
+ "import * as dep from \"@goscript/example.test/interface-registry/dep/index.js\"",
1006
+ "import \"@goscript/example.test/interface-registry/dep/index.js\"",
1007
+ "import * as __goscript_local from \"./local.gs.ts\"",
1008
+ "import \"./local.gs.ts\"",
1009
+ "case $.typeAssert<Exclude<__goscript_local.Local, null>>(__goscriptTypeSwitchValue, \"main.Local\").ok",
1010
+ "$.typeAssertTuple<dep.Remote | null>(v, \"dep.Remote\")",
1011
+ } {
1012
+ if !strings.Contains(mainText, want) {
1013
+ t.Fatalf("missing %q in main output:\n%s", want, mainText)
1014
+ }
1015
+ }
1016
+
1017
+ depIndexContent, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "interface-registry", "dep", "index.ts"))
1018
+ if err != nil {
1019
+ t.Fatal(err.Error())
1020
+ }
1021
+ if !strings.Contains(string(depIndexContent), "import \"./dep.gs.ts\"") {
1022
+ t.Fatalf("missing interface side-effect import in dep index:\n%s", string(depIndexContent))
1023
+ }
1024
+ }
1025
+
1026
+ func TestCompilePackagesEmitsIndexAddressRefs(t *testing.T) {
1027
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1028
+ "go.mod": "module example.test/indexaddr\n\ngo 1.25.3\n",
1029
+ "main.go": strings.Join([]string{
1030
+ "package main",
1031
+ "type Item struct { N int }",
1032
+ "func set(ptr *int, value int) {",
1033
+ " *ptr = value",
1034
+ "}",
1035
+ "func Use(values []int, i int) int {",
1036
+ " set(&values[i], 9)",
1037
+ " return values[i]",
1038
+ "}",
1039
+ "func Items() []*Item {",
1040
+ " return []*Item{{N: 1}}",
1041
+ "}",
1042
+ "",
1043
+ }, "\n"),
1044
+ })
1045
+ outputDir := filepath.Join(t.TempDir(), "output")
1046
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1047
+ if err != nil {
1048
+ t.Fatal(err.Error())
1049
+ }
1050
+
1051
+ _, err = comp.CompilePackages(context.Background(), ".")
1052
+ if err != nil {
1053
+ t.Fatal(err.Error())
1054
+ }
1055
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "indexaddr", "main.gs.ts")
1056
+ content, err := os.ReadFile(outputFile)
1057
+ if err != nil {
1058
+ t.Fatal(err.Error())
1059
+ }
1060
+ text := string(content)
1061
+ if !strings.Contains(text, "set($.indexRef(values!, i), 9)") {
1062
+ t.Fatalf("missing index address reference:\n%s", text)
1063
+ }
1064
+ if !strings.Contains(text, "new Item({N: 1})") {
1065
+ t.Fatalf("missing elided pointer composite literal:\n%s", text)
1066
+ }
1067
+ }
1068
+
1069
+ func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
1070
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1071
+ "go.mod": "module example.test/structs\n\ngo 1.25.3\n",
1072
+ "main.go": strings.Join([]string{
1073
+ "package main",
1074
+ "type Counter struct {",
1075
+ " // Value counts reads.",
1076
+ " Value int `json:\"value\"`",
1077
+ " ID ObjectID",
1078
+ "}",
1079
+ "type ObjectID int32",
1080
+ "func (c Counter) Read() int {",
1081
+ " return c.Value",
1082
+ "}",
1083
+ "func (c *Counter) Set(v int) {",
1084
+ " c.Value = v",
1085
+ "}",
1086
+ "func NewCounter() *Counter {",
1087
+ " return &Counter{Value: 3}",
1088
+ "}",
1089
+ "func main() {",
1090
+ " original := Counter{Value: 1}",
1091
+ "",
1092
+ " // Copy should stay readable in generated output.",
1093
+ " copy := original",
1094
+ " pointer := &original",
1095
+ " pointer.Set(2)",
1096
+ " NewCounter().Set(5)",
1097
+ " var iface any = pointer",
1098
+ " _, ok := iface.(*Counter)",
1099
+ " println(copy.Read(), original.Read(), ok)",
1100
+ "}",
1101
+ "",
1102
+ }, "\n"),
1103
+ })
1104
+ outputDir := filepath.Join(t.TempDir(), "output")
1105
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1106
+ if err != nil {
1107
+ t.Fatal(err.Error())
1108
+ }
1109
+
1110
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1111
+ t.Fatal(err.Error())
1112
+ }
1113
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "structs", "main.gs.ts")
1114
+ content, err := os.ReadFile(outputFile)
1115
+ if err != nil {
1116
+ t.Fatal(err.Error())
1117
+ }
1118
+ text := string(content)
1119
+ for _, want := range []string{
1120
+ "export class Counter",
1121
+ "// Value counts reads.\n\tpublic get Value(): number",
1122
+ "public clone(): Counter",
1123
+ "public Read(): number",
1124
+ "public Set(v: number): void",
1125
+ "let original = $.varRef($.markAsStructValue(new Counter({Value: 1})))",
1126
+ "let original = $.varRef($.markAsStructValue(new Counter({Value: 1})))\n\n\t// Copy should stay readable in generated output.\n\tlet copy",
1127
+ "let copy = $.markAsStructValue($.cloneStructValue(original.value))",
1128
+ "let pointer: Counter | $.VarRef<Counter> | null = original",
1129
+ "Counter.prototype.Set.call(pointer, 2)",
1130
+ "Counter.prototype.Set.call(NewCounter(), 5)",
1131
+ "let [, ok] = $.typeAssertTuple<Counter | $.VarRef<Counter> | null>(iface, { kind: $.TypeKind.Pointer, elemType: \"main.Counter\" })",
1132
+ "\"Value\": { type: { kind: $.TypeKind.Basic, name: \"int\" }, tag: \"json:\\\"value\\\"\" }",
1133
+ "\"ID\": { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.ObjectID\" }",
1134
+ } {
1135
+ if !strings.Contains(text, want) {
1136
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ func TestCompilePackagesClonesNestedStructFieldsWithCloneMethodCollision(t *testing.T) {
1142
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1143
+ "go.mod": "module example.test/nestedclone\n\ngo 1.25.3\n",
1144
+ "main.go": strings.Join([]string{
1145
+ "package main",
1146
+ "type Box struct { Value int }",
1147
+ "func (b *Box) clone() (*Box, error) {",
1148
+ " return &Box{Value: b.Value + 1}, nil",
1149
+ "}",
1150
+ "type Holder struct { Box Box }",
1151
+ "func copyHolder(h Holder) Holder { return h }",
1152
+ "func main() {",
1153
+ " _ = copyHolder(Holder{Box: Box{Value: 1}})",
1154
+ "}",
1155
+ "",
1156
+ }, "\n"),
1157
+ })
1158
+ outputDir := filepath.Join(t.TempDir(), "output")
1159
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1160
+ if err != nil {
1161
+ t.Fatal(err.Error())
1162
+ }
1163
+
1164
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1165
+ t.Fatal(err.Error())
1166
+ }
1167
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "nestedclone", "main.gs.ts"))
1168
+ if err != nil {
1169
+ t.Fatal(err.Error())
1170
+ }
1171
+ text := string(content)
1172
+ for _, want := range []string{
1173
+ "public __goscriptClone(): Box",
1174
+ "Box: $.varRef(init?.Box ? $.markAsStructValue($.cloneStructValue(init.Box)) : $.markAsStructValue(new Box()))",
1175
+ "Box: $.varRef($.markAsStructValue($.cloneStructValue(this._fields.Box.value)))",
1176
+ } {
1177
+ if !strings.Contains(text, want) {
1178
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1179
+ }
1180
+ }
1181
+ if strings.Contains(text, "init.Box.clone()") || strings.Contains(text, "this._fields.Box.value.clone()") {
1182
+ t.Fatalf("nested struct-field clone bypassed cloneStructValue:\n%s", text)
1183
+ }
1184
+ }
1185
+
1186
+ func TestCompilePackagesEmitsNestedPointerStorageAssertions(t *testing.T) {
1187
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1188
+ "go.mod": "module example.test/pointers\n\ngo 1.25.3\n",
1189
+ "main.go": strings.Join([]string{
1190
+ "package main",
1191
+ "func main() {",
1192
+ " var x int = 10",
1193
+ " p1 := &x",
1194
+ " p2 := &p1",
1195
+ " p3 := &p2",
1196
+ " ***p3 = 12",
1197
+ " println(x)",
1198
+ "}",
1199
+ "",
1200
+ }, "\n"),
1201
+ })
1202
+ outputDir := filepath.Join(t.TempDir(), "output")
1203
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1204
+ if err != nil {
1205
+ t.Fatal(err.Error())
1206
+ }
1207
+
1208
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1209
+ t.Fatal(err.Error())
1210
+ }
1211
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "pointers", "main.gs.ts")
1212
+ content, err := os.ReadFile(outputFile)
1213
+ if err != nil {
1214
+ t.Fatal(err.Error())
1215
+ }
1216
+ text := string(content)
1217
+ for _, want := range []string{
1218
+ "let x: $.VarRef<number> = $.varRef(10)",
1219
+ "let p1 = $.varRef(x)",
1220
+ "let p2 = $.varRef(p1)",
1221
+ "let p3 = p2",
1222
+ "$.pointerValue<$.VarRef<number> | null>($.pointerValue<$.VarRef<$.VarRef<number> | null> | null>(p3))!.value = 12",
636
1223
  } {
637
1224
  if !strings.Contains(text, want) {
638
1225
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -642,28 +1229,127 @@ func TestCompilePackagesEmitsNestedPointerStorageAssertions(t *testing.T) {
642
1229
 
643
1230
  func TestCompilePackagesEmitsArraySliceMapStringAndNamedMethods(t *testing.T) {
644
1231
  moduleDir := writePackageGraphFixture(t, map[string]string{
645
- "go.mod": "module example.test/collections\n\ngo 1.25.3\n",
1232
+ "go.mod": "module example.test/collections\n\ngo 1.25.3\n",
1233
+ "main.go": strings.Join([]string{
1234
+ "package main",
1235
+ "type MyInt int",
1236
+ "func (m MyInt) Double() int { return int(m) * 2 }",
1237
+ "type MySlice []int",
1238
+ "func (s *MySlice) Add(v int) { *s = append(*s, v) }",
1239
+ "func main() {",
1240
+ " arr := [3]int{1: 10}",
1241
+ " slice := make([]int, 0, 2)",
1242
+ " empty := []rune{}",
1243
+ " literal := []int{1, 2}",
1244
+ " literal = append(literal, 3)",
1245
+ " slice = append(slice, 5)",
1246
+ " slice[0] = arr[1]",
1247
+ " m := make(map[string]int)",
1248
+ " m[\"one\"] = 1",
1249
+ " value, ok := m[\"missing\"]",
1250
+ " text := \"hé\"",
1251
+ " var list MySlice",
1252
+ " list.Add(7)",
1253
+ " println(arr[1], slice[0], literal[2], len(slice), cap(slice), len(empty), value, ok, text[0], text[1], MyInt(5).Double(), len(list))",
1254
+ "}",
1255
+ "",
1256
+ }, "\n"),
1257
+ })
1258
+ outputDir := filepath.Join(t.TempDir(), "output")
1259
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1260
+ if err != nil {
1261
+ t.Fatal(err.Error())
1262
+ }
1263
+
1264
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1265
+ t.Fatal(err.Error())
1266
+ }
1267
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "collections", "main.gs.ts")
1268
+ content, err := os.ReadFile(outputFile)
1269
+ if err != nil {
1270
+ t.Fatal(err.Error())
1271
+ }
1272
+ text := string(content)
1273
+ for _, want := range []string{
1274
+ "export type MyInt = number",
1275
+ "export function MyInt_Double(m: MyInt): number",
1276
+ "export type MySlice = $.Slice<number>",
1277
+ "export function MySlice_Add(s: $.VarRef<MySlice> | null, v: number): void",
1278
+ "let arr = [0, 10, 0]",
1279
+ "let slice: $.Slice<number> = $.makeSlice<number>(0, 2, \"number\")",
1280
+ "let empty: $.Slice<number> = $.arrayToSlice<number>([])",
1281
+ "let literal: $.Slice<number> = $.arrayToSlice<number>([1, 2])",
1282
+ "literal = $.append(literal, 3)",
1283
+ "slice![0] = arr[1]",
1284
+ "let m: Map<string, number> | null = $.makeMap<string, number>()",
1285
+ "$.mapSet(m, \"one\", 1)",
1286
+ "let [value, ok] = $.mapGet(m, \"missing\", 0)",
1287
+ "slice![0]",
1288
+ "literal![2]",
1289
+ "let list: $.VarRef<MySlice> = $.varRef(null as MySlice)",
1290
+ "MySlice_Add(list, 7)",
1291
+ "$.indexStringOrBytes(text, 0)",
1292
+ "MyInt_Double(5)",
1293
+ } {
1294
+ if !strings.Contains(text, want) {
1295
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1296
+ }
1297
+ }
1298
+ }
1299
+
1300
+ func TestCompilePackagesWrapsAddressedMapRangeValue(t *testing.T) {
1301
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1302
+ "go.mod": "module example.test/maprange\n\ngo 1.25.3\n",
1303
+ "main.go": strings.Join([]string{
1304
+ "package main",
1305
+ "type Item struct { Values []string }",
1306
+ "func (i *Item) add(v string) { i.Values = append(i.Values, v) }",
1307
+ "func Apply(m map[int]Item, values []string) {",
1308
+ " for k, item := range m {",
1309
+ " for _, v := range values {",
1310
+ " item.add(v)",
1311
+ " }",
1312
+ " m[k] = item",
1313
+ " }",
1314
+ "}",
1315
+ "",
1316
+ }, "\n"),
1317
+ })
1318
+ outputDir := filepath.Join(t.TempDir(), "output")
1319
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1320
+ if err != nil {
1321
+ t.Fatal(err.Error())
1322
+ }
1323
+
1324
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1325
+ t.Fatal(err.Error())
1326
+ }
1327
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "maprange", "main.gs.ts")
1328
+ content, err := os.ReadFile(outputFile)
1329
+ if err != nil {
1330
+ t.Fatal(err.Error())
1331
+ }
1332
+ text := string(content)
1333
+ for _, want := range []string{
1334
+ "let item: $.VarRef<Item> = $.varRef($.markAsStructValue($.cloneStructValue(__goscriptRangeValue",
1335
+ "item.value.add(v)",
1336
+ "$.mapSet(m, k, $.markAsStructValue($.cloneStructValue(item.value)))",
1337
+ } {
1338
+ if !strings.Contains(text, want) {
1339
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1340
+ }
1341
+ }
1342
+ }
1343
+
1344
+ func TestCompilePackagesLowersPromotedNamedPrimitiveMethod(t *testing.T) {
1345
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1346
+ "go.mod": "module example.test/promotedprimitive\n\ngo 1.25.3\n",
646
1347
  "main.go": strings.Join([]string{
647
1348
  "package main",
648
- "type MyInt int",
649
- "func (m MyInt) Double() int { return int(m) * 2 }",
650
- "type MySlice []int",
651
- "func (s *MySlice) Add(v int) { *s = append(*s, v) }",
652
- "func main() {",
653
- " arr := [3]int{1: 10}",
654
- " slice := make([]int, 0, 2)",
655
- " empty := []rune{}",
656
- " literal := []int{1, 2}",
657
- " literal = append(literal, 3)",
658
- " slice = append(slice, 5)",
659
- " slice[0] = arr[1]",
660
- " m := make(map[string]int)",
661
- " m[\"one\"] = 1",
662
- " value, ok := m[\"missing\"]",
663
- " text := \"hé\"",
664
- " var list MySlice",
665
- " list.Add(7)",
666
- " println(arr[1], slice[0], literal[2], len(slice), cap(slice), len(empty), value, ok, text[0], text[1], MyInt(5).Double(), len(list))",
1349
+ "import \"time\"",
1350
+ "type Lifetime struct { time.Duration }",
1351
+ "func Seconds(l Lifetime) float64 {",
1352
+ " return l.Seconds()",
667
1353
  "}",
668
1354
  "",
669
1355
  }, "\n"),
@@ -677,32 +1363,309 @@ func TestCompilePackagesEmitsArraySliceMapStringAndNamedMethods(t *testing.T) {
677
1363
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
678
1364
  t.Fatal(err.Error())
679
1365
  }
680
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "collections", "main.gs.ts")
1366
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "promotedprimitive", "main.gs.ts")
1367
+ content, err := os.ReadFile(outputFile)
1368
+ if err != nil {
1369
+ t.Fatal(err.Error())
1370
+ }
1371
+ text := string(content)
1372
+ if !strings.Contains(text, "return time.Duration_Seconds(l.Duration)") {
1373
+ t.Fatalf("promoted named primitive method was not lowered through the owner function:\n%s", text)
1374
+ }
1375
+ if strings.Contains(text, ".Seconds()") {
1376
+ t.Fatalf("promoted named primitive method still used a JavaScript member call:\n%s", text)
1377
+ }
1378
+ }
1379
+
1380
+ func TestCompilePackagesMarksPackageFunctionVariablesAsync(t *testing.T) {
1381
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1382
+ "go.mod": "module example.test/packagefuncvar\n\ngo 1.25.3\n",
1383
+ "dep/dep.go": strings.Join([]string{
1384
+ "package dep",
1385
+ "func parse(raw string) (int, error) { return len(raw), nil }",
1386
+ "var Parse = parse",
1387
+ "",
1388
+ }, "\n"),
1389
+ "main.go": strings.Join([]string{
1390
+ "package main",
1391
+ "import \"example.test/packagefuncvar/dep\"",
1392
+ "func parse() (int, error) {",
1393
+ " return dep.Parse(\"turn\")",
1394
+ "}",
1395
+ "",
1396
+ }, "\n"),
1397
+ })
1398
+ outputDir := filepath.Join(t.TempDir(), "output")
1399
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1400
+ if err != nil {
1401
+ t.Fatal(err.Error())
1402
+ }
1403
+
1404
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1405
+ t.Fatal(err.Error())
1406
+ }
1407
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "packagefuncvar", "main.gs.ts")
1408
+ content, err := os.ReadFile(outputFile)
1409
+ if err != nil {
1410
+ t.Fatal(err.Error())
1411
+ }
1412
+ text := string(content)
1413
+ if !strings.Contains(text, "export async function parse(): globalThis.Promise<[number, $.GoError]>") {
1414
+ t.Fatalf("package function variable caller was not marked async:\n%s", text)
1415
+ }
1416
+ if !strings.Contains(text, "return await dep.Parse!(\"turn\")") {
1417
+ t.Fatalf("package function variable call was not awaited:\n%s", text)
1418
+ }
1419
+ }
1420
+
1421
+ func TestCompilePackagesAwaitsImportedAsyncMethodsAndFunctions(t *testing.T) {
1422
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1423
+ "go.mod": "module example.test/importedasync\n\ngo 1.25.3\n",
1424
+ "dep/dep.go": strings.Join([]string{
1425
+ "package dep",
1426
+ "type Host struct{ ch chan struct{} }",
1427
+ "func (h *Host) Snapshot() (int, error) { <-h.ch; return 1, nil }",
1428
+ "func Read(h *Host) (int, error) { return h.Snapshot() }",
1429
+ "",
1430
+ }, "\n"),
1431
+ "dep/dep_test.go": strings.Join([]string{
1432
+ "package dep",
1433
+ "import \"testing\"",
1434
+ "func TestHost(t *testing.T) {}",
1435
+ "",
1436
+ }, "\n"),
1437
+ "main.go": strings.Join([]string{
1438
+ "package main",
1439
+ "import \"example.test/importedasync/dep\"",
1440
+ "func UseMethod(h *dep.Host) (int, error) {",
1441
+ " return h.Snapshot()",
1442
+ "}",
1443
+ "func UseFunction(h *dep.Host) (int, error) {",
1444
+ " return dep.Read(h)",
1445
+ "}",
1446
+ "",
1447
+ }, "\n"),
1448
+ "main_test.go": strings.Join([]string{
1449
+ "package main",
1450
+ "import \"testing\"",
1451
+ "func TestUse(t *testing.T) {}",
1452
+ "",
1453
+ }, "\n"),
1454
+ })
1455
+ outputDir := filepath.Join(t.TempDir(), "output")
1456
+ service := NewCompileService()
1457
+ _, err := service.Compile(context.Background(), &CompileRequest{
1458
+ Patterns: []string{".", "./dep"},
1459
+ Dir: moduleDir,
1460
+ OutputPath: outputDir,
1461
+ DependencyMode: DependencyModeAll,
1462
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
1463
+ Tests: true,
1464
+ AllDependencies: true,
1465
+ })
1466
+ if err != nil {
1467
+ t.Fatal(err.Error())
1468
+ }
1469
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "importedasync", "main.gs.ts")
1470
+ content, err := os.ReadFile(outputFile)
1471
+ if err != nil {
1472
+ t.Fatal(err.Error())
1473
+ }
1474
+ text := string(content)
1475
+ if !strings.Contains(text, "export async function UseMethod") {
1476
+ t.Fatalf("imported async method caller was not marked async:\n%s", text)
1477
+ }
1478
+ if !strings.Contains(text, "return await dep.Host.prototype.Snapshot.call(h)") {
1479
+ t.Fatalf("imported async method call was not awaited:\n%s", text)
1480
+ }
1481
+ if !strings.Contains(text, "export async function UseFunction") {
1482
+ t.Fatalf("imported async function caller was not marked async:\n%s", text)
1483
+ }
1484
+ if !strings.Contains(text, "return await dep.Read(h)") {
1485
+ t.Fatalf("imported async function call was not awaited:\n%s", text)
1486
+ }
1487
+ }
1488
+
1489
+ func TestCompilePackagesCastsConvertedTupleCallSpreads(t *testing.T) {
1490
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1491
+ "go.mod": "module example.test/tuplecallspread\n\ngo 1.25.3\n",
1492
+ "main.go": strings.Join([]string{
1493
+ "package main",
1494
+ "type Resolver interface { Resolve() }",
1495
+ "type relay struct{}",
1496
+ "func (*relay) Resolve() {}",
1497
+ "func NewRelay() (*relay, error) { return &relay{}, nil }",
1498
+ "func R(res Resolver, err error) ([]Resolver, error) {",
1499
+ " if err != nil { return nil, err }",
1500
+ " return []Resolver{res}, nil",
1501
+ "}",
1502
+ "func Use() ([]Resolver, error) {",
1503
+ " return R(NewRelay())",
1504
+ "}",
1505
+ "",
1506
+ }, "\n"),
1507
+ })
1508
+ outputDir := filepath.Join(t.TempDir(), "output")
1509
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1510
+ if err != nil {
1511
+ t.Fatal(err.Error())
1512
+ }
1513
+
1514
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1515
+ t.Fatal(err.Error())
1516
+ }
1517
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "tuplecallspread", "main.gs.ts")
1518
+ content, err := os.ReadFile(outputFile)
1519
+ if err != nil {
1520
+ t.Fatal(err.Error())
1521
+ }
1522
+ text := string(content)
1523
+ if !strings.Contains(text, "return R(...(() => {") ||
1524
+ !strings.Contains(text, "] as [Resolver | null, $.GoError]") {
1525
+ t.Fatalf("converted tuple call spread was not cast as a TypeScript tuple:\n%s", text)
1526
+ }
1527
+ }
1528
+
1529
+ func TestCompilePackagesPreservesBlankNamedResultSlots(t *testing.T) {
1530
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1531
+ "go.mod": "module example.test/blankresult\n\ngo 1.25.3\n",
1532
+ "main.go": strings.Join([]string{
1533
+ "package main",
1534
+ "func values() (first, second bool, _ error) {",
1535
+ " first = true",
1536
+ " return",
1537
+ "}",
1538
+ "",
1539
+ }, "\n"),
1540
+ })
1541
+ outputDir := filepath.Join(t.TempDir(), "output")
1542
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1543
+ if err != nil {
1544
+ t.Fatal(err.Error())
1545
+ }
1546
+
1547
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1548
+ t.Fatal(err.Error())
1549
+ }
1550
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "blankresult", "main.gs.ts")
1551
+ content, err := os.ReadFile(outputFile)
1552
+ if err != nil {
1553
+ t.Fatal(err.Error())
1554
+ }
1555
+ text := string(content)
1556
+ if !strings.Contains(text, "return [first, second, null as $.GoError]") {
1557
+ t.Fatalf("blank named result slot was not preserved in naked return:\n%s", text)
1558
+ }
1559
+ }
1560
+
1561
+ func TestCompilePackagesCastsGenericMethodResultAssignments(t *testing.T) {
1562
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1563
+ "go.mod": "module example.test/genericmethodresult\n\ngo 1.25.3\n",
1564
+ "main.go": strings.Join([]string{
1565
+ "package main",
1566
+ "import (",
1567
+ " \"errors\"",
1568
+ " \"sync/atomic\"",
1569
+ ")",
1570
+ "func load() error {",
1571
+ " var ptr atomic.Pointer[error]",
1572
+ " err := errors.New(\"boom\")",
1573
+ " ptr.Store(&err)",
1574
+ " if errp := ptr.Load(); errp != nil {",
1575
+ " return *errp",
1576
+ " }",
1577
+ " return nil",
1578
+ "}",
1579
+ "",
1580
+ }, "\n"),
1581
+ })
1582
+ outputDir := filepath.Join(t.TempDir(), "output")
1583
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1584
+ if err != nil {
1585
+ t.Fatal(err.Error())
1586
+ }
1587
+
1588
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1589
+ t.Fatal(err.Error())
1590
+ }
1591
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "genericmethodresult", "main.gs.ts")
1592
+ content, err := os.ReadFile(outputFile)
1593
+ if err != nil {
1594
+ t.Fatal(err.Error())
1595
+ }
1596
+ text := string(content)
1597
+ if !strings.Contains(text, "(ptr.value.Load() as $.VarRef<$.GoError> | null)") {
1598
+ t.Fatalf("generic method result assignment was not cast to the target type:\n%s", text)
1599
+ }
1600
+ }
1601
+
1602
+ func TestCompilePackagesPreservesFloatConversionLiterals(t *testing.T) {
1603
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1604
+ "go.mod": "module example.test/floatconv\n\ngo 1.25.3\n",
1605
+ "main.go": strings.Join([]string{
1606
+ "package main",
1607
+ "import \"math\"",
1608
+ "func value() float64 {",
1609
+ " return math.Pow(float64(0.69314718056), 2)",
1610
+ "}",
1611
+ "",
1612
+ }, "\n"),
1613
+ })
1614
+ outputDir := filepath.Join(t.TempDir(), "output")
1615
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1616
+ if err != nil {
1617
+ t.Fatal(err.Error())
1618
+ }
1619
+
1620
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1621
+ t.Fatal(err.Error())
1622
+ }
1623
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "floatconv", "main.gs.ts")
1624
+ content, err := os.ReadFile(outputFile)
1625
+ if err != nil {
1626
+ t.Fatal(err.Error())
1627
+ }
1628
+ text := string(content)
1629
+ if strings.Contains(text, "$.int(0.69314718056)") {
1630
+ t.Fatalf("float64 literal conversion was truncated through $.int:\n%s", text)
1631
+ }
1632
+ if !strings.Contains(text, "math.Pow(0.69314718056, 2)") {
1633
+ t.Fatalf("missing direct float literal conversion:\n%s", text)
1634
+ }
1635
+ }
1636
+
1637
+ func TestCompilePackagesLowersStringOrderingThroughRuntime(t *testing.T) {
1638
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1639
+ "go.mod": "module example.test/stringorder\n\ngo 1.25.3\n",
1640
+ "main.go": strings.Join([]string{
1641
+ "package main",
1642
+ "func less(left, right []byte) bool {",
1643
+ " return string(left) < string(right)",
1644
+ "}",
1645
+ "func ordered(left, right string) bool {",
1646
+ " return left <= right",
1647
+ "}",
1648
+ "",
1649
+ }, "\n"),
1650
+ })
1651
+ outputDir := filepath.Join(t.TempDir(), "output")
1652
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1653
+ if err != nil {
1654
+ t.Fatal(err.Error())
1655
+ }
1656
+
1657
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1658
+ t.Fatal(err.Error())
1659
+ }
1660
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "stringorder", "main.gs.ts")
681
1661
  content, err := os.ReadFile(outputFile)
682
1662
  if err != nil {
683
1663
  t.Fatal(err.Error())
684
1664
  }
685
1665
  text := string(content)
686
1666
  for _, want := range []string{
687
- "export type MyInt = number",
688
- "export function MyInt_Double(m: MyInt): number",
689
- "export type MySlice = $.Slice<number>",
690
- "export function MySlice_Add(s: $.VarRef<MySlice> | null, v: number): void",
691
- "let arr = [0, 10, 0]",
692
- "let slice = $.makeSlice<number>(0, 2, \"number\")",
693
- "let empty = $.arrayToSlice<number>([])",
694
- "let literal = $.arrayToSlice<number>([1, 2])",
695
- "literal = $.append(literal, 3)",
696
- "slice![0] = arr[1]",
697
- "let m: Map<string, number> | null = $.makeMap<string, number>()",
698
- "$.mapSet(m, \"one\", 1)",
699
- "let [value, ok] = $.mapGet(m, \"missing\", 0)",
700
- "slice![0]",
701
- "literal![2]",
702
- "let list: $.VarRef<MySlice> = $.varRef(null as MySlice)",
703
- "MySlice_Add(list, 7)",
704
- "$.indexStringOrBytes(text, 0)",
705
- "MyInt_Double(5)",
1667
+ "$.stringCompare(left, right) < 0",
1668
+ "$.stringCompare(left, right) <= 0",
706
1669
  } {
707
1670
  if !strings.Contains(text, want) {
708
1671
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -759,18 +1722,18 @@ func TestCompilePackagesEmitsInterfacesMethodValuesTypeSwitchesAndFunctionAssert
759
1722
  text := string(content)
760
1723
  for _, want := range []string{
761
1724
  "export type Greeter = ((name: string) => string | globalThis.Promise<string>) | null",
762
- "export type ReadCloser = null | {",
1725
+ "export type ReadCloser = {",
763
1726
  "Read(): string",
764
1727
  "Close(): string",
765
1728
  "$.registerInterfaceType(\n\t\"main.ReadCloser\"",
766
1729
  "((__receiver) => () => __receiver.Inc())($.pointerValue<Counter>(counter))",
767
1730
  "$.namedFunction(greet, \"main.Greeter\")",
768
- "$.typedNil(\"*struct{Name string}\")",
1731
+ "$.interfaceValue<any>(null, \"*struct{Name string}\")",
769
1732
  "elemType: { kind: $.TypeKind.Struct, methods: [], fields: {\"Name\": { kind: $.TypeKind.Basic, name: \"string\" }} }",
770
1733
  "let fn = __goscriptTuple",
771
1734
  "switch (true)",
772
- "case $.typeAssert<ReadCloser | null>(__goscriptTypeSwitchValue, \"main.ReadCloser\").ok",
773
- "let v: ReadCloser | null = $.typeAssert<ReadCloser | null>(__goscriptTypeSwitchValue, \"main.ReadCloser\").value",
1735
+ "case $.typeAssert<Exclude<ReadCloser, null>>(__goscriptTypeSwitchValue, \"main.ReadCloser\").ok",
1736
+ "let v: Exclude<ReadCloser, null> = $.typeAssert<Exclude<ReadCloser, null>>(__goscriptTypeSwitchValue, \"main.ReadCloser\").value",
774
1737
  } {
775
1738
  if !strings.Contains(text, want) {
776
1739
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -805,15 +1768,67 @@ func TestCompilePackagesAssertsInterfaceMethodReceivers(t *testing.T) {
805
1768
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
806
1769
  t.Fatal(err.Error())
807
1770
  }
808
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "interface-receivers", "main.gs.ts")
1771
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "interface-receivers", "main.gs.ts")
1772
+ content, err := os.ReadFile(outputFile)
1773
+ if err != nil {
1774
+ t.Fatal(err.Error())
1775
+ }
1776
+ text := string(content)
1777
+ for _, want := range []string{
1778
+ "export type FileInfo = {",
1779
+ "$.println($.pointerValue<Exclude<FileInfo, null>>(info).Name())",
1780
+ } {
1781
+ if !strings.Contains(text, want) {
1782
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1783
+ }
1784
+ }
1785
+ }
1786
+
1787
+ func TestCompilePackagesUsesNonNilInterfaceTypeSwitchCaseVarRefs(t *testing.T) {
1788
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1789
+ "go.mod": "module example.test/interface-switch-varref\n\ngo 1.25.3\n",
1790
+ "main.go": strings.Join([]string{
1791
+ "package main",
1792
+ "type rawConn interface { Control() error }",
1793
+ "type tcpConn interface {",
1794
+ " SyscallConn() (rawConn, error)",
1795
+ " SetLinger(int) error",
1796
+ "}",
1797
+ "type Conn struct { c rawConn }",
1798
+ "func keep(*tcpConn) {}",
1799
+ "func NewConn(c any) (*Conn, error) {",
1800
+ " cc := &Conn{}",
1801
+ " var err error",
1802
+ " switch c := c.(type) {",
1803
+ " case tcpConn:",
1804
+ " keep(&c)",
1805
+ " cc.c, err = c.SyscallConn()",
1806
+ " }",
1807
+ " return cc, err",
1808
+ "}",
1809
+ "",
1810
+ }, "\n"),
1811
+ })
1812
+ outputDir := filepath.Join(t.TempDir(), "output")
1813
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1814
+ if err != nil {
1815
+ t.Fatal(err.Error())
1816
+ }
1817
+
1818
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1819
+ t.Fatal(err.Error())
1820
+ }
1821
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "interface-switch-varref", "main.gs.ts")
809
1822
  content, err := os.ReadFile(outputFile)
810
1823
  if err != nil {
811
1824
  t.Fatal(err.Error())
812
1825
  }
813
1826
  text := string(content)
814
1827
  for _, want := range []string{
815
- "export type FileInfo = null | {",
816
- "$.println($.pointerValue<Exclude<FileInfo, null>>(info).Name())",
1828
+ "export type tcpConn = {",
1829
+ "case $.typeAssert<Exclude<tcpConn, null>>(__goscriptTypeSwitchValue, \"main.tcpConn\").ok",
1830
+ "let c: $.VarRef<Exclude<tcpConn, null>> = $.varRef($.typeAssert<Exclude<tcpConn, null>>(__goscriptTypeSwitchValue, \"main.tcpConn\").value)",
1831
+ "$.pointerValue<Exclude<tcpConn, null>>(c.value).SyscallConn()",
817
1832
  } {
818
1833
  if !strings.Contains(text, want) {
819
1834
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -893,6 +1908,9 @@ func TestCompilePackagesEmitsGenericMethodsAliasesAndDictionaries(t *testing.T)
893
1908
  " var zero T",
894
1909
  " return zero",
895
1910
  "}",
1911
+ "func Copy[T any](vals ...T) []T {",
1912
+ " return append([]T{}, vals...)",
1913
+ "}",
896
1914
  "func main() {",
897
1915
  " box := NewBox(7)",
898
1916
  " println(box.Get())",
@@ -924,13 +1942,18 @@ func TestCompilePackagesEmitsGenericMethodsAliasesAndDictionaries(t *testing.T)
924
1942
  for _, want := range []string{
925
1943
  "public Get(): any",
926
1944
  "export function NewBox(__typeArgs: $.GenericTypeArgs | undefined, value: any): Box",
1945
+ "export function ZeroValue(__typeArgs: $.GenericTypeArgs | undefined): any",
1946
+ "export function CallString(__typeArgs: $.GenericTypeArgs | undefined, v: any): string",
1947
+ "export function Sum<T>(__typeArgs: $.GenericTypeArgs | undefined, vals: $.Slice<T>): any",
1948
+ "export function Copy<T>(__typeArgs: $.GenericTypeArgs | undefined, vals: $.Slice<T>): $.Slice<T>",
1949
+ "return $.append($.arrayToSlice<T>([]), ...(vals ?? []))",
927
1950
  "let seen: Set = $.makeMap<number, {}>()",
928
1951
  "$.mapSet(seen, 1, {})",
929
1952
  "$.genericZero(__typeArgs, \"T\", null)",
930
1953
  "$.callGenericMethod(__typeArgs, \"T\", \"String\", v)",
931
- "ZeroValue({T: { type: \"main.MyInt\", zero: () => 0, methods: {String: MyInt_String} }})",
932
- "CallString({T: { type: \"main.MyInt\", zero: () => 0, methods: {String: MyInt_String} }}, zero)",
933
- "Sum({T: { type: \"main.MyInt\", zero: () => 0, methods: {String: MyInt_String} }}, null)",
1954
+ "ZeroValue({T: { type: { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.MyInt\" }, zero: () => 0, methods: {String: (receiver: any, ...args: any[]) => (MyInt_String as any)($.pointerValue(receiver), ...args)} }})",
1955
+ "CallString({T: { type: { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.MyInt\" }, zero: () => 0, methods: {String: (receiver: any, ...args: any[]) => (MyInt_String as any)($.pointerValue(receiver), ...args)} }}, zero)",
1956
+ "Sum({T: { type: { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.MyInt\" }, zero: () => 0, methods: {String: (receiver: any, ...args: any[]) => (MyInt_String as any)($.pointerValue(receiver), ...args)} }}, null)",
934
1957
  } {
935
1958
  if !strings.Contains(text, want) {
936
1959
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -1015,8 +2038,8 @@ func TestCompilePackagesEmitsRecursiveFunctionTypeInfo(t *testing.T) {
1015
2038
  }
1016
2039
  text := string(content)
1017
2040
  for _, want := range []string{
1018
- "export type Handler = ((_p0: ((_p0: Handler) => Handler | globalThis.Promise<Handler>) | null) => Handler | globalThis.Promise<Handler>) | null",
1019
- "\"Next\": { kind: $.TypeKind.Function, name: \"main.Handler\"",
2041
+ "export type Handler = ((_p0: ((_p0: Handler | null) => Handler | null | globalThis.Promise<Handler | null>) | null) => Handler | null | globalThis.Promise<Handler | null>) | null",
2042
+ "\"Next\": ({ kind: $.TypeKind.Function, name: \"main.Handler\"",
1020
2043
  "params: [{ kind: $.TypeKind.Function, params: [], results: [] }]",
1021
2044
  "results: [{ kind: $.TypeKind.Function, params: [], results: [] }]",
1022
2045
  } {
@@ -1082,7 +2105,7 @@ func TestCompilePackagesPacksVariadicCalls(t *testing.T) {
1082
2105
  "export type Collector = ((label: string, parts: $.Slice<string>) => string | globalThis.Promise<string>) | null",
1083
2106
  "Join(parts: $.Slice<string>): string",
1084
2107
  "export function collect(label: string, parts: $.Slice<string>): string",
1085
- "let part = parts![__rangeIndex]",
2108
+ "let part = __goscriptRangeTarget0![__rangeIndex]",
1086
2109
  "export function maybeErr(parts: $.Slice<string>): $.GoError",
1087
2110
  "public Join(parts: $.Slice<string>): string",
1088
2111
  "collect(\"none\", null)",
@@ -1130,7 +2153,7 @@ func TestCompilePackagesPacksVariadicCallsInGeneratedSubpackage(t *testing.T) {
1130
2153
  t.Fatal(err.Error())
1131
2154
  }
1132
2155
  text := string(content)
1133
- want := "$.pointerValue<State>(s).SetErrorf(\"bad %q\", $.arrayToSlice<any>([key]))"
2156
+ want := "State.prototype.SetErrorf.call(s, \"bad %q\", $.arrayToSlice<any>([key]))"
1134
2157
  if !strings.Contains(text, want) {
1135
2158
  t.Fatalf("missing %q in generated output:\n%s", want, text)
1136
2159
  }
@@ -1139,6 +2162,112 @@ func TestCompilePackagesPacksVariadicCallsInGeneratedSubpackage(t *testing.T) {
1139
2162
  }
1140
2163
  }
1141
2164
 
2165
+ func TestCompilePackagesImportsSelectedExternalFieldTypes(t *testing.T) {
2166
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2167
+ "go.mod": "module example.test/selected-field-import\n\ngo 1.25.3\n",
2168
+ "dep/dep.go": strings.Join([]string{
2169
+ "package dep",
2170
+ "type URL struct { Path string }",
2171
+ "",
2172
+ }, "\n"),
2173
+ "api/api.go": strings.Join([]string{
2174
+ "package api",
2175
+ "import \"example.test/selected-field-import/dep\"",
2176
+ "type Request struct { URL *dep.URL }",
2177
+ "",
2178
+ }, "\n"),
2179
+ "main.go": strings.Join([]string{
2180
+ "package main",
2181
+ "import \"example.test/selected-field-import/api\"",
2182
+ "func requestPath(r *api.Request) string {",
2183
+ " return r.URL.Path",
2184
+ "}",
2185
+ "",
2186
+ }, "\n"),
2187
+ })
2188
+ outputDir := filepath.Join(t.TempDir(), "output")
2189
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir, AllDependencies: true}, nil, nil)
2190
+ if err != nil {
2191
+ t.Fatal(err.Error())
2192
+ }
2193
+
2194
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2195
+ t.Fatal(err.Error())
2196
+ }
2197
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "selected-field-import", "main.gs.ts")
2198
+ content, err := os.ReadFile(outputFile)
2199
+ if err != nil {
2200
+ t.Fatal(err.Error())
2201
+ }
2202
+ text := string(content)
2203
+ for _, want := range []string{
2204
+ "import * as dep from \"@goscript/example.test/selected-field-import/dep/index.js\"",
2205
+ "$.pointerValue<dep.URL>($.pointerValue<api.Request>(r).URL).Path",
2206
+ } {
2207
+ if !strings.Contains(text, want) {
2208
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2209
+ }
2210
+ }
2211
+ }
2212
+
2213
+ func TestCompilePackagesErasesUnavailableOverrideFieldTypes(t *testing.T) {
2214
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2215
+ "go.mod": "module example.test/override-field-type\n\ngo 1.25.3\n",
2216
+ "dep/dep.go": strings.Join([]string{
2217
+ "package dep",
2218
+ "type URL struct { Path string }",
2219
+ "",
2220
+ }, "\n"),
2221
+ "api/api.go": strings.Join([]string{
2222
+ "package api",
2223
+ "import \"example.test/override-field-type/dep\"",
2224
+ "type Request struct { URL *dep.URL }",
2225
+ "",
2226
+ }, "\n"),
2227
+ "main.go": strings.Join([]string{
2228
+ "package main",
2229
+ "import \"example.test/override-field-type/api\"",
2230
+ "func requestPath(r *api.Request) string {",
2231
+ " return r.URL.Path",
2232
+ "}",
2233
+ "",
2234
+ }, "\n"),
2235
+ })
2236
+ overrideDir := filepath.Join(t.TempDir(), "gs")
2237
+ writeFixtureFile(t, overrideDir, "example.test/override-field-type/api/index.ts", strings.Join([]string{
2238
+ "export class Request {",
2239
+ " public URL: any = null",
2240
+ "}",
2241
+ "",
2242
+ }, "\n"))
2243
+ outputDir := filepath.Join(t.TempDir(), "output")
2244
+ comp, err := NewCompiler(&Config{
2245
+ Dir: moduleDir,
2246
+ OutputPath: outputDir,
2247
+ AllDependencies: true,
2248
+ OverrideDirs: []string{overrideDir},
2249
+ }, nil, nil)
2250
+ if err != nil {
2251
+ t.Fatal(err.Error())
2252
+ }
2253
+
2254
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2255
+ t.Fatal(err.Error())
2256
+ }
2257
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "override-field-type", "main.gs.ts")
2258
+ content, err := os.ReadFile(outputFile)
2259
+ if err != nil {
2260
+ t.Fatal(err.Error())
2261
+ }
2262
+ text := string(content)
2263
+ if !strings.Contains(text, "$.pointerValue<any>($.pointerValue<api.Request>(r).URL).Path") {
2264
+ t.Fatalf("missing erased override field type in generated output:\n%s", text)
2265
+ }
2266
+ if strings.Contains(text, "dep.URL") || strings.Contains(text, "pointerValue<URL>") {
2267
+ t.Fatalf("generated output referenced unavailable dependency type:\n%s", text)
2268
+ }
2269
+ }
2270
+
1142
2271
  func TestCompilePackagesLowersRangeOverFunctionIterators(t *testing.T) {
1143
2272
  moduleDir := writePackageGraphFixture(t, map[string]string{
1144
2273
  "go.mod": "module example.test/iterators\n\ngo 1.25.3\n",
@@ -1216,6 +2345,49 @@ func TestCompilePackagesLowersRangeOverFunctionIterators(t *testing.T) {
1216
2345
  }
1217
2346
  }
1218
2347
 
2348
+ func TestCompilePackagesPreservesNamedFunctionResultTypes(t *testing.T) {
2349
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2350
+ "go.mod": "module example.test/namedfuncresult\n\ngo 1.25.3\n",
2351
+ "main.go": strings.Join([]string{
2352
+ "package main",
2353
+ "type Seq func(func(int) bool)",
2354
+ "func values() Seq {",
2355
+ " return nil",
2356
+ "}",
2357
+ "func main() {",
2358
+ " if values() == nil {",
2359
+ " println(\"empty\")",
2360
+ " }",
2361
+ "}",
2362
+ "",
2363
+ }, "\n"),
2364
+ })
2365
+ outputDir := filepath.Join(t.TempDir(), "output")
2366
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2367
+ if err != nil {
2368
+ t.Fatal(err.Error())
2369
+ }
2370
+
2371
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2372
+ t.Fatal(err.Error())
2373
+ }
2374
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "namedfuncresult", "main.gs.ts")
2375
+ content, err := os.ReadFile(outputFile)
2376
+ if err != nil {
2377
+ t.Fatal(err.Error())
2378
+ }
2379
+ text := string(content)
2380
+ for _, want := range []string{
2381
+ "export type Seq = ((_p0: ((_p0: number) => boolean | globalThis.Promise<boolean>) | null) => void) | null",
2382
+ "export function values(): Seq | null",
2383
+ "return (null as Seq | null)",
2384
+ } {
2385
+ if !strings.Contains(text, want) {
2386
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2387
+ }
2388
+ }
2389
+ }
2390
+
1219
2391
  func TestCompilePackagesLowersFunctionIteratorControlFlow(t *testing.T) {
1220
2392
  moduleDir := writePackageGraphFixture(t, map[string]string{
1221
2393
  "go.mod": "module example.test/iterator-control\n\ngo 1.25.3\n",
@@ -1258,17 +2430,203 @@ func TestCompilePackagesLowersFunctionIteratorControlFlow(t *testing.T) {
1258
2430
  " }",
1259
2431
  " return -1",
1260
2432
  "}",
1261
- "func nestedReturn(limit int) int {",
1262
- " for v := range values {",
1263
- " for i := range values {",
1264
- " if i == limit {",
1265
- " return v + i",
1266
- " }",
1267
- " }",
2433
+ "func nestedReturn(limit int) int {",
2434
+ " for v := range values {",
2435
+ " for i := range values {",
2436
+ " if i == limit {",
2437
+ " return v + i",
2438
+ " }",
2439
+ " }",
2440
+ " }",
2441
+ " return -1",
2442
+ "}",
2443
+ "func main() { println(first(3), nestedReturn(2)) }",
2444
+ "",
2445
+ }, "\n"),
2446
+ })
2447
+ outputDir := filepath.Join(t.TempDir(), "output")
2448
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2449
+ if err != nil {
2450
+ t.Fatal(err.Error())
2451
+ }
2452
+
2453
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2454
+ t.Fatal(err.Error())
2455
+ }
2456
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "iterator-control", "main.gs.ts")
2457
+ content, err := os.ReadFile(outputFile)
2458
+ if err != nil {
2459
+ t.Fatal(err.Error())
2460
+ }
2461
+ text := string(content)
2462
+ for _, want := range []string{
2463
+ "let __goscriptRangeReturn",
2464
+ "let __goscriptRangeReturnValue",
2465
+ "return true",
2466
+ "return false",
2467
+ "__goscriptRangeReturnValue",
2468
+ "if (__goscriptRangeReturn",
2469
+ "return __goscriptRangeReturnValue",
2470
+ "for (let i = 0; i < j; [i, j] = [i + 1, j - 1])",
2471
+ "for (let i = 0; i < 2; i++)",
2472
+ "switch (v)",
2473
+ "const __goscriptTypeSwitchValue",
2474
+ "switch (true)",
2475
+ "case $.typeAssert<number>(__goscriptTypeSwitchValue, { kind: $.TypeKind.Basic, name: \"int\" }).ok",
2476
+ "break",
2477
+ } {
2478
+ if !strings.Contains(text, want) {
2479
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2480
+ }
2481
+ }
2482
+ nestedReturn := regexp.MustCompile(`if \(__goscriptRangeReturn\d+\) \{\n\t+__goscriptRangeReturn\d+ = true\n\t+__goscriptRangeReturnValue\d+ = __goscriptRangeReturnValue\d+!\n\t+return false\n\t+\}`)
2483
+ if !nestedReturn.MatchString(text) {
2484
+ t.Fatalf("missing nested range return propagation:\n%s", text)
2485
+ }
2486
+ }
2487
+
2488
+ func TestCompilePackagesEmitsAsyncChannelsSelectAndDefer(t *testing.T) {
2489
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2490
+ "go.mod": "module example.test/async\n\ngo 1.25.3\n",
2491
+ "main.go": strings.Join([]string{
2492
+ "package main",
2493
+ "type Processor interface { Process(v int) int }",
2494
+ "type Worker struct { ch chan int }",
2495
+ "func (w *Worker) Process(v int) int {",
2496
+ " w.ch <- v",
2497
+ " return <-w.ch",
2498
+ "}",
2499
+ "func call(p Processor) int { return p.Process(2) }",
2500
+ "func stopLoop(stop chan struct{}, done chan struct{}) {",
2501
+ " for {",
2502
+ " select {",
2503
+ " case <-stop:",
2504
+ " done <- struct{}{}",
2505
+ " return",
2506
+ " }",
2507
+ " }",
2508
+ "}",
2509
+ "func main() {",
2510
+ " ch := make(chan int, 1)",
2511
+ " defer func() { <-ch }()",
2512
+ " go func() { ch <- 1 }()",
2513
+ " select {",
2514
+ " case v := <-ch:",
2515
+ " println(v)",
2516
+ " default:",
2517
+ " println(\"default\")",
2518
+ " }",
2519
+ " _ = call(&Worker{ch: make(chan int, 1)})",
2520
+ "}",
2521
+ "",
2522
+ }, "\n"),
2523
+ })
2524
+ outputDir := filepath.Join(t.TempDir(), "output")
2525
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2526
+ if err != nil {
2527
+ t.Fatal(err.Error())
2528
+ }
2529
+
2530
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2531
+ t.Fatal(err.Error())
2532
+ }
2533
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "async", "main.gs.ts")
2534
+ content, err := os.ReadFile(outputFile)
2535
+ if err != nil {
2536
+ t.Fatal(err.Error())
2537
+ }
2538
+ text := string(content)
2539
+ for _, want := range []string{
2540
+ "Process(v: number): number | globalThis.Promise<number>",
2541
+ "public async Process(v: number): globalThis.Promise<number>",
2542
+ "let ch = $.makeChannel<number>(1, 0, \"both\")",
2543
+ "await $.chanSend($.pointerValue<Worker>(w).ch, v)",
2544
+ "return await $.chanRecv($.pointerValue<Worker>(w).ch)",
2545
+ "await using __defer = new $.AsyncDisposableStack()",
2546
+ "queueMicrotask(async () => { await ($.functionValue(async (): globalThis.Promise<void> => {",
2547
+ "$.selectStatement<any, void>([",
2548
+ "let v = __goscriptSelect1Result.value",
2549
+ "return $.selectVoidReturn()",
2550
+ "await call($.interfaceValue<Processor | null>(new Worker({ch: $.makeChannel<number>(1, 0, \"both\")}), \"*main.Worker\"))",
2551
+ } {
2552
+ if !strings.Contains(text, want) {
2553
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2554
+ }
2555
+ }
2556
+ }
2557
+
2558
+ func TestCompilePackagesMarksSelectReturningIfElseCasesUnreachable(t *testing.T) {
2559
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2560
+ "go.mod": "module example.test/select-if-else\n\ngo 1.25.3\n",
2561
+ "main.go": strings.Join([]string{
2562
+ "package main",
2563
+ "import \"context\"",
2564
+ "func finish(ctx context.Context, ch <-chan int, client bool) (int, error) {",
2565
+ " select {",
2566
+ " case <-ch:",
2567
+ " if client {",
2568
+ " return 1, nil",
2569
+ " } else {",
2570
+ " return 2, nil",
2571
+ " }",
2572
+ " case <-ctx.Done():",
2573
+ " return 3, ctx.Err()",
2574
+ " }",
2575
+ "}",
2576
+ "",
2577
+ }, "\n"),
2578
+ })
2579
+ outputDir := filepath.Join(t.TempDir(), "output")
2580
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2581
+ if err != nil {
2582
+ t.Fatal(err.Error())
2583
+ }
2584
+
2585
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2586
+ t.Fatal(err.Error())
2587
+ }
2588
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "select-if-else", "main.gs.ts")
2589
+ content, err := os.ReadFile(outputFile)
2590
+ if err != nil {
2591
+ t.Fatal(err.Error())
2592
+ }
2593
+ text := string(content)
2594
+ for _, want := range []string{
2595
+ "export async function finish(ctx: context.Context | null, ch: $.Channel<number> | null, client: boolean): globalThis.Promise<[number, $.GoError]>",
2596
+ "if (__goscriptSelect0HasReturn) {",
2597
+ "throw new Error(\"unreachable select\")",
2598
+ } {
2599
+ if !strings.Contains(text, want) {
2600
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2601
+ }
2602
+ }
2603
+ }
2604
+
2605
+ func TestCompilePackagesPropagatesImmediateFuncLitAsync(t *testing.T) {
2606
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2607
+ "go.mod": "module example.test/immediate-func-lit-async\n\ngo 1.25.3\n",
2608
+ "main.go": strings.Join([]string{
2609
+ "package main",
2610
+ "import \"sync\"",
2611
+ "type resolver struct {",
2612
+ " parent *resolver",
2613
+ " mutex sync.Mutex",
2614
+ "}",
2615
+ "func (r *resolver) lookup() (int, error) {",
2616
+ " value := func() int {",
2617
+ " r.mutex.Lock()",
2618
+ " defer r.mutex.Unlock()",
2619
+ " return 7",
2620
+ " }()",
2621
+ " if r.parent != nil {",
2622
+ " return r.parent.lookup()",
1268
2623
  " }",
1269
- " return -1",
2624
+ " return value, nil",
1270
2625
  "}",
1271
- "func main() { println(first(3), nestedReturn(2)) }",
2626
+ "func use(r *resolver) (int, error) {",
2627
+ " return r.lookup()",
2628
+ "}",
2629
+ "func main() {}",
1272
2630
  "",
1273
2631
  }, "\n"),
1274
2632
  })
@@ -1281,61 +2639,44 @@ func TestCompilePackagesLowersFunctionIteratorControlFlow(t *testing.T) {
1281
2639
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1282
2640
  t.Fatal(err.Error())
1283
2641
  }
1284
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "iterator-control", "main.gs.ts")
2642
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "immediate-func-lit-async", "main.gs.ts")
1285
2643
  content, err := os.ReadFile(outputFile)
1286
2644
  if err != nil {
1287
2645
  t.Fatal(err.Error())
1288
2646
  }
1289
2647
  text := string(content)
1290
2648
  for _, want := range []string{
1291
- "let __goscriptRangeReturn",
1292
- "let __goscriptRangeReturnValue",
1293
- "return true",
1294
- "return false",
1295
- "__goscriptRangeReturnValue",
1296
- "if (__goscriptRangeReturn",
1297
- "return __goscriptRangeReturnValue",
1298
- "for (let i = 0; i < j; [i, j] = [i + 1, j - 1])",
1299
- "for (let i = 0; i < 2; i++)",
1300
- "switch (v)",
1301
- "const __goscriptTypeSwitchValue",
1302
- "switch (true)",
1303
- "case $.typeAssert<number>(__goscriptTypeSwitchValue, { kind: $.TypeKind.Basic, name: \"int\" }).ok",
1304
- "break",
2649
+ "public async lookup(): globalThis.Promise<[number, $.GoError]>",
2650
+ "return await resolver.prototype.lookup.call($.pointerValue<resolver>(r).parent)",
2651
+ "export async function use(r: resolver | $.VarRef<resolver> | null): globalThis.Promise<[number, $.GoError]>",
2652
+ "return await resolver.prototype.lookup.call(r)",
1305
2653
  } {
1306
2654
  if !strings.Contains(text, want) {
1307
2655
  t.Fatalf("missing %q in generated output:\n%s", want, text)
1308
2656
  }
1309
2657
  }
1310
- nestedReturn := regexp.MustCompile(`if \(__goscriptRangeReturn\d+\) \{\n\t+__goscriptRangeReturn\d+ = true\n\t+__goscriptRangeReturnValue\d+ = __goscriptRangeReturnValue\d+!\n\t+return false\n\t+\}`)
1311
- if !nestedReturn.MatchString(text) {
1312
- t.Fatalf("missing nested range return propagation:\n%s", text)
2658
+ if strings.Contains(text, "const __goscriptReturn0 = resolver.prototype.lookup.call") {
2659
+ t.Fatalf("immediate func-literal async method call was not awaited:\n%s", text)
1313
2660
  }
1314
2661
  }
1315
2662
 
1316
- func TestCompilePackagesEmitsAsyncChannelsSelectAndDefer(t *testing.T) {
2663
+ func TestCompilePackagesParenthesizesAsyncFieldReceivers(t *testing.T) {
1317
2664
  moduleDir := writePackageGraphFixture(t, map[string]string{
1318
- "go.mod": "module example.test/async\n\ngo 1.25.3\n",
2665
+ "go.mod": "module example.test/asyncfield\n\ngo 1.25.3\n",
1319
2666
  "main.go": strings.Join([]string{
1320
2667
  "package main",
1321
- "type Processor interface { Process(v int) int }",
1322
- "type Worker struct { ch chan int }",
1323
- "func (w *Worker) Process(v int) int {",
1324
- " w.ch <- v",
1325
- " return <-w.ch",
2668
+ "type Result struct { ok bool }",
2669
+ "type Box struct { ch chan int }",
2670
+ "func (b *Box) next() Result {",
2671
+ " b.ch <- 1",
2672
+ " return Result{ok: true}",
2673
+ "}",
2674
+ "func (b *Box) OK() bool {",
2675
+ " return b.next().ok",
1326
2676
  "}",
1327
- "func call(p Processor) int { return p.Process(2) }",
1328
2677
  "func main() {",
1329
- " ch := make(chan int, 1)",
1330
- " defer func() { <-ch }()",
1331
- " go func() { ch <- 1 }()",
1332
- " select {",
1333
- " case v := <-ch:",
1334
- " println(v)",
1335
- " default:",
1336
- " println(\"default\")",
1337
- " }",
1338
- " _ = call(&Worker{ch: make(chan int, 1)})",
2678
+ " box := &Box{ch: make(chan int, 1)}",
2679
+ " println(box.OK())",
1339
2680
  "}",
1340
2681
  "",
1341
2682
  }, "\n"),
@@ -1349,27 +2690,17 @@ func TestCompilePackagesEmitsAsyncChannelsSelectAndDefer(t *testing.T) {
1349
2690
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1350
2691
  t.Fatal(err.Error())
1351
2692
  }
1352
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "async", "main.gs.ts")
2693
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "asyncfield", "main.gs.ts")
1353
2694
  content, err := os.ReadFile(outputFile)
1354
2695
  if err != nil {
1355
2696
  t.Fatal(err.Error())
1356
2697
  }
1357
2698
  text := string(content)
1358
- for _, want := range []string{
1359
- "Process(v: number): globalThis.Promise<number>",
1360
- "public async Process(v: number): globalThis.Promise<number>",
1361
- "let ch = $.makeChannel<number>(1, 0, \"both\")",
1362
- "await $.chanSend($.pointerValue<Worker>(w).ch, v)",
1363
- "return await $.chanRecv($.pointerValue<Worker>(w).ch)",
1364
- "await using __defer = new $.AsyncDisposableStack()",
1365
- "queueMicrotask(async () => { await ($.functionValue(async (): globalThis.Promise<void> => {",
1366
- "$.selectStatement<any, void>([",
1367
- "let v = result.value",
1368
- "await call($.interfaceValue<Processor | null>(new Worker({ch: $.makeChannel<number>(1, 0, \"both\")}), \"*main.Worker\"))",
1369
- } {
1370
- if !strings.Contains(text, want) {
1371
- t.Fatalf("missing %q in generated output:\n%s", want, text)
1372
- }
2699
+ if !strings.Contains(text, "return (await Box.prototype.next.call(b)).ok") {
2700
+ t.Fatalf("async field receiver was not parenthesized:\n%s", text)
2701
+ }
2702
+ if strings.Contains(text, "return await Box.prototype.next.call(b).ok") {
2703
+ t.Fatalf("async field receiver selected the promise before await:\n%s", text)
1373
2704
  }
1374
2705
  }
1375
2706
 
@@ -1650,6 +2981,298 @@ func TestCompilePackagesLowersUnaryBitwiseComplement(t *testing.T) {
1650
2981
  }
1651
2982
  }
1652
2983
 
2984
+ func TestCompilePackagesParenthesizesRepeatedUnarySigns(t *testing.T) {
2985
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2986
+ "go.mod": "module example.test/unary-signs\n\ngo 1.25.3\n",
2987
+ "constants.go": strings.Join([]string{
2988
+ "package main",
2989
+ "const extOffset = -0x1000",
2990
+ "",
2991
+ }, "\n"),
2992
+ "main.go": strings.Join([]string{
2993
+ "package main",
2994
+ "type Extension int32",
2995
+ "type LoadExtension struct { Num Extension }",
2996
+ "func Decode(k int32) LoadExtension {",
2997
+ " return LoadExtension{Num: Extension(-extOffset + k)}",
2998
+ "}",
2999
+ "",
3000
+ }, "\n"),
3001
+ })
3002
+ outputDir := filepath.Join(t.TempDir(), "output")
3003
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3004
+ if err != nil {
3005
+ t.Fatal(err.Error())
3006
+ }
3007
+
3008
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3009
+ t.Fatal(err.Error())
3010
+ }
3011
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "unary-signs", "main.gs.ts")
3012
+ content, err := os.ReadFile(outputFile)
3013
+ if err != nil {
3014
+ t.Fatal(err.Error())
3015
+ }
3016
+ text := string(content)
3017
+ if strings.Contains(text, "--4096") {
3018
+ t.Fatalf("generated invalid decrement token:\n%s", text)
3019
+ }
3020
+ if !strings.Contains(text, "-(-4096) + k") {
3021
+ t.Fatalf("missing parenthesized negative constant:\n%s", text)
3022
+ }
3023
+ }
3024
+
3025
+ func TestCompilePackagesNormalizesWideIntegerReturnTargets(t *testing.T) {
3026
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3027
+ "go.mod": "module example.test/wide-return\n\ngo 1.25.3\n",
3028
+ "main.go": strings.Join([]string{
3029
+ "package main",
3030
+ "import \"hash\"",
3031
+ "func Read(h hash.Hash64) uint64 {",
3032
+ " return h.Sum64()",
3033
+ "}",
3034
+ "",
3035
+ }, "\n"),
3036
+ })
3037
+ outputDir := filepath.Join(t.TempDir(), "output")
3038
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3039
+ if err != nil {
3040
+ t.Fatal(err.Error())
3041
+ }
3042
+
3043
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3044
+ t.Fatal(err.Error())
3045
+ }
3046
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "wide-return", "main.gs.ts")
3047
+ content, err := os.ReadFile(outputFile)
3048
+ if err != nil {
3049
+ t.Fatal(err.Error())
3050
+ }
3051
+ text := string(content)
3052
+ if !strings.Contains(text, "return $.uint($.pointerValue<Exclude<hash.Hash64, null>>(h).Sum64(), 64)") {
3053
+ t.Fatalf("missing uint64 return normalization:\n%s", text)
3054
+ }
3055
+ }
3056
+
3057
+ func TestCompilePackagesUnwrapsImportedVarRefValueMethodReceiver(t *testing.T) {
3058
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3059
+ "go.mod": "module example.test/imported-varref-receiver\n\ngo 1.25.3\n",
3060
+ "dep/dep.go": strings.Join([]string{
3061
+ "package dep",
3062
+ "type Info struct { Count int }",
3063
+ "func (i Info) Enabled() bool { return i.Count > 0 }",
3064
+ "func addInfo(i *Info) { i.Count = 1 }",
3065
+ "var CPU Info",
3066
+ "func init() { addInfo(&CPU) }",
3067
+ "",
3068
+ }, "\n"),
3069
+ "main.go": strings.Join([]string{
3070
+ "package main",
3071
+ "import \"example.test/imported-varref-receiver/dep\"",
3072
+ "func Enabled() bool {",
3073
+ " return dep.CPU.Enabled()",
3074
+ "}",
3075
+ "",
3076
+ }, "\n"),
3077
+ })
3078
+ outputDir := filepath.Join(t.TempDir(), "output")
3079
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3080
+ if err != nil {
3081
+ t.Fatal(err.Error())
3082
+ }
3083
+
3084
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3085
+ t.Fatal(err.Error())
3086
+ }
3087
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "imported-varref-receiver", "main.gs.ts")
3088
+ content, err := os.ReadFile(outputFile)
3089
+ if err != nil {
3090
+ t.Fatal(err.Error())
3091
+ }
3092
+ text := string(content)
3093
+ if !strings.Contains(text, "$.cloneStructValue($.pointerValue<dep.Info>(dep.CPU))") {
3094
+ t.Fatalf("missing imported VarRef receiver unwrap:\n%s", text)
3095
+ }
3096
+ if strings.Contains(text, "$.cloneStructValue(dep.CPU))") {
3097
+ t.Fatalf("imported VarRef receiver stayed wrapped:\n%s", text)
3098
+ }
3099
+ }
3100
+
3101
+ func TestCompilePackagesUnwrapsImportedArrayPackageVarReads(t *testing.T) {
3102
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3103
+ "go.mod": "module example.test/imported-array-var\n\ngo 1.25.3\n",
3104
+ "dep/dep.go": strings.Join([]string{
3105
+ "package dep",
3106
+ "var Table = [2]int{3, 5}",
3107
+ "func touch(v *[2]int) { v[0]++ }",
3108
+ "func init() { touch(&Table) }",
3109
+ "func Sum(v [2]int) int { return v[0] + v[1] }",
3110
+ "",
3111
+ }, "\n"),
3112
+ "main.go": strings.Join([]string{
3113
+ "package main",
3114
+ "import \"example.test/imported-array-var/dep\"",
3115
+ "func Read() int {",
3116
+ " return dep.Table[1] + dep.Sum(dep.Table)",
3117
+ "}",
3118
+ "",
3119
+ }, "\n"),
3120
+ })
3121
+ outputDir := filepath.Join(t.TempDir(), "output")
3122
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3123
+ if err != nil {
3124
+ t.Fatal(err.Error())
3125
+ }
3126
+
3127
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3128
+ t.Fatal(err.Error())
3129
+ }
3130
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "imported-array-var", "main.gs.ts")
3131
+ content, err := os.ReadFile(outputFile)
3132
+ if err != nil {
3133
+ t.Fatal(err.Error())
3134
+ }
3135
+ text := string(content)
3136
+ for _, want := range []string{
3137
+ "$.pointerValue<number[]>(dep.Table)[1]",
3138
+ "dep.Sum($.pointerValue<number[]>(dep.Table))",
3139
+ } {
3140
+ if !strings.Contains(text, want) {
3141
+ t.Fatalf("missing imported array package var read %q:\n%s", want, text)
3142
+ }
3143
+ }
3144
+ }
3145
+
3146
+ func TestCompilePackagesAddressesImportedArrayPackageVarsAsRefs(t *testing.T) {
3147
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3148
+ "go.mod": "module example.test/imported-array-var-address\n\ngo 1.25.3\n",
3149
+ "dep/dep.go": strings.Join([]string{
3150
+ "package dep",
3151
+ "var Table = [2]int{3, 5}",
3152
+ "func touch(v *[2]int) { v[0]++ }",
3153
+ "func init() { touch(&Table) }",
3154
+ "func SumPtr(v *[2]int) int { return v[0] + v[1] }",
3155
+ "",
3156
+ }, "\n"),
3157
+ "main.go": strings.Join([]string{
3158
+ "package main",
3159
+ "import \"example.test/imported-array-var-address/dep\"",
3160
+ "func Read() int {",
3161
+ " return dep.SumPtr(&dep.Table)",
3162
+ "}",
3163
+ "",
3164
+ }, "\n"),
3165
+ })
3166
+ outputDir := filepath.Join(t.TempDir(), "output")
3167
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3168
+ if err != nil {
3169
+ t.Fatal(err.Error())
3170
+ }
3171
+
3172
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3173
+ t.Fatal(err.Error())
3174
+ }
3175
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "imported-array-var-address", "main.gs.ts")
3176
+ content, err := os.ReadFile(outputFile)
3177
+ if err != nil {
3178
+ t.Fatal(err.Error())
3179
+ }
3180
+ text := string(content)
3181
+ if !strings.Contains(text, "dep.SumPtr(dep.Table)") {
3182
+ t.Fatalf("missing imported array package var address:\n%s", text)
3183
+ }
3184
+ if strings.Contains(text, "dep.SumPtr($.pointerValue<number[]>(dep.Table))") ||
3185
+ strings.Contains(text, "dep._fields.Table") {
3186
+ t.Fatalf("imported array package var address was lowered as a read or field:\n%s", text)
3187
+ }
3188
+ }
3189
+
3190
+ func TestCompilePackagesUnwrapsAliasedArrayPackageVarReads(t *testing.T) {
3191
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3192
+ "go.mod": "module example.test/aliased-array-var\n\ngo 1.25.3\n",
3193
+ "table.go": strings.Join([]string{
3194
+ "package main",
3195
+ "var Table = [2]int{3, 5}",
3196
+ "func touch(v *[2]int) { v[0]++ }",
3197
+ "func init() { touch(&Table) }",
3198
+ "func Sum(v [2]int) int { return v[0] + v[1] }",
3199
+ "",
3200
+ }, "\n"),
3201
+ "read.go": strings.Join([]string{
3202
+ "package main",
3203
+ "func Read() int {",
3204
+ " return Table[1] + Sum(Table)",
3205
+ "}",
3206
+ "",
3207
+ }, "\n"),
3208
+ })
3209
+ outputDir := filepath.Join(t.TempDir(), "output")
3210
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3211
+ if err != nil {
3212
+ t.Fatal(err.Error())
3213
+ }
3214
+
3215
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3216
+ t.Fatal(err.Error())
3217
+ }
3218
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "aliased-array-var", "read.gs.ts")
3219
+ content, err := os.ReadFile(outputFile)
3220
+ if err != nil {
3221
+ t.Fatal(err.Error())
3222
+ }
3223
+ text := string(content)
3224
+ for _, want := range []string{
3225
+ "$.pointerValue<number[]>(__goscript_table.Table)[1]",
3226
+ "Sum($.pointerValue<number[]>(__goscript_table.Table))",
3227
+ } {
3228
+ if !strings.Contains(text, want) {
3229
+ t.Fatalf("missing aliased array package var read %q:\n%s", want, text)
3230
+ }
3231
+ }
3232
+ }
3233
+
3234
+ func TestCompilePackagesAddressesAliasedArrayPackageVarsAsRefs(t *testing.T) {
3235
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3236
+ "go.mod": "module example.test/aliased-array-var-address\n\ngo 1.25.3\n",
3237
+ "table.go": strings.Join([]string{
3238
+ "package main",
3239
+ "var Table = [2]int{3, 5}",
3240
+ "func touch(v *[2]int) { v[0]++ }",
3241
+ "func init() { touch(&Table) }",
3242
+ "func SumPtr(v *[2]int) int { return v[0] + v[1] }",
3243
+ "",
3244
+ }, "\n"),
3245
+ "read.go": strings.Join([]string{
3246
+ "package main",
3247
+ "func Read() int {",
3248
+ " return SumPtr(&Table)",
3249
+ "}",
3250
+ "",
3251
+ }, "\n"),
3252
+ })
3253
+ outputDir := filepath.Join(t.TempDir(), "output")
3254
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3255
+ if err != nil {
3256
+ t.Fatal(err.Error())
3257
+ }
3258
+
3259
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3260
+ t.Fatal(err.Error())
3261
+ }
3262
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "aliased-array-var-address", "read.gs.ts")
3263
+ content, err := os.ReadFile(outputFile)
3264
+ if err != nil {
3265
+ t.Fatal(err.Error())
3266
+ }
3267
+ text := string(content)
3268
+ if !strings.Contains(text, "SumPtr(__goscript_table.Table)") {
3269
+ t.Fatalf("missing aliased array package var address:\n%s", text)
3270
+ }
3271
+ if strings.Contains(text, "SumPtr($.pointerValue<number[]>(__goscript_table.Table))") {
3272
+ t.Fatalf("aliased array package var address was lowered as a read:\n%s", text)
3273
+ }
3274
+ }
3275
+
1653
3276
  func TestCompileSourceToTypeScriptCompilesSingleFile(t *testing.T) {
1654
3277
  output, err := CompileSourceToTypeScript("package main\nfunc main() { println(\"hi\") }\n", "main")
1655
3278
  if err != nil {