goscript 0.0.83 → 0.1.0

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 (197) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +27 -7
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +86 -0
  39. package/dist/gs/builtin/hostio.js +266 -0
  40. package/dist/gs/builtin/hostio.js.map +1 -0
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/print.d.ts +8 -0
  45. package/dist/gs/builtin/print.js +111 -0
  46. package/dist/gs/builtin/print.js.map +1 -0
  47. package/dist/gs/builtin/slice.d.ts +1 -1
  48. package/dist/gs/builtin/slice.js.map +1 -1
  49. package/dist/gs/builtin/type.d.ts +11 -0
  50. package/dist/gs/builtin/type.js +55 -1
  51. package/dist/gs/builtin/type.js.map +1 -1
  52. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  53. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  54. package/dist/gs/bytes/reader.gs.js.map +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/crypto/rand/index.d.ts +5 -0
  57. package/dist/gs/crypto/rand/index.js +77 -0
  58. package/dist/gs/crypto/rand/index.js.map +1 -0
  59. package/dist/gs/encoding/json/index.d.ts +3 -0
  60. package/dist/gs/encoding/json/index.js +160 -0
  61. package/dist/gs/encoding/json/index.js.map +1 -0
  62. package/dist/gs/fmt/fmt.js +2 -22
  63. package/dist/gs/fmt/fmt.js.map +1 -1
  64. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  65. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  66. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  67. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  68. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  69. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  70. package/dist/gs/go/scanner/index.d.ts +29 -0
  71. package/dist/gs/go/scanner/index.js +120 -0
  72. package/dist/gs/go/scanner/index.js.map +1 -0
  73. package/dist/gs/go/token/index.d.ts +31 -0
  74. package/dist/gs/go/token/index.js +82 -0
  75. package/dist/gs/go/token/index.js.map +1 -0
  76. package/dist/gs/internal/abi/index.js.map +1 -1
  77. package/dist/gs/io/fs/fs.js.map +1 -1
  78. package/dist/gs/io/fs/readdir.js.map +1 -1
  79. package/dist/gs/io/fs/readfile.js.map +1 -1
  80. package/dist/gs/io/fs/stat.js.map +1 -1
  81. package/dist/gs/io/fs/sub.js.map +1 -1
  82. package/dist/gs/io/io.js.map +1 -1
  83. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  84. package/dist/gs/os/error.gs.js +2 -4
  85. package/dist/gs/os/error.gs.js.map +1 -1
  86. package/dist/gs/os/exec.gs.js.map +1 -1
  87. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  88. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  89. package/dist/gs/os/root_js.gs.js.map +1 -1
  90. package/dist/gs/os/tempfile.gs.js +66 -9
  91. package/dist/gs/os/tempfile.gs.js.map +1 -1
  92. package/dist/gs/os/types.gs.js.map +1 -1
  93. package/dist/gs/os/types_js.gs.d.ts +2 -51
  94. package/dist/gs/os/types_js.gs.js +67 -105
  95. package/dist/gs/os/types_js.gs.js.map +1 -1
  96. package/dist/gs/os/types_unix.gs.js.map +1 -1
  97. package/dist/gs/path/filepath/match.js.map +1 -1
  98. package/dist/gs/path/match.js.map +1 -1
  99. package/dist/gs/path/path.js.map +1 -1
  100. package/dist/gs/reflect/index.d.ts +2 -2
  101. package/dist/gs/reflect/index.js +1 -1
  102. package/dist/gs/reflect/index.js.map +1 -1
  103. package/dist/gs/reflect/map.js.map +1 -1
  104. package/dist/gs/reflect/type.d.ts +2 -1
  105. package/dist/gs/reflect/type.js +85 -14
  106. package/dist/gs/reflect/type.js.map +1 -1
  107. package/dist/gs/reflect/types.js.map +1 -1
  108. package/dist/gs/reflect/visiblefields.js.map +1 -1
  109. package/dist/gs/runtime/runtime.js.map +1 -1
  110. package/dist/gs/sort/sort.gs.js.map +1 -1
  111. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  112. package/dist/gs/strconv/quote.gs.js.map +1 -1
  113. package/dist/gs/strings/builder.js.map +1 -1
  114. package/dist/gs/strings/reader.js.map +1 -1
  115. package/dist/gs/strings/replace.js.map +1 -1
  116. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  117. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  118. package/dist/gs/sync/sync.d.ts +1 -0
  119. package/dist/gs/sync/sync.js +12 -0
  120. package/dist/gs/sync/sync.js.map +1 -1
  121. package/dist/gs/time/time.js.map +1 -1
  122. package/dist/gs/unicode/unicode.js.map +1 -1
  123. package/go.mod +2 -2
  124. package/gs/builtin/builtin.ts +31 -6
  125. package/gs/builtin/hostio.test.ts +246 -0
  126. package/gs/builtin/hostio.ts +413 -0
  127. package/gs/builtin/index.ts +1 -0
  128. package/gs/builtin/print.test.ts +48 -0
  129. package/gs/builtin/print.ts +154 -0
  130. package/gs/builtin/runtime-contract.test.ts +230 -0
  131. package/gs/builtin/type.ts +84 -1
  132. package/gs/crypto/rand/index.test.ts +32 -0
  133. package/gs/crypto/rand/index.ts +90 -0
  134. package/gs/crypto/rand/meta.json +5 -0
  135. package/gs/encoding/json/index.test.ts +65 -0
  136. package/gs/encoding/json/index.ts +186 -0
  137. package/gs/fmt/fmt.test.ts +41 -30
  138. package/gs/fmt/fmt.ts +2 -22
  139. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  140. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  141. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  142. package/gs/go/scanner/index.test.ts +50 -0
  143. package/gs/go/scanner/index.ts +157 -0
  144. package/gs/go/token/index.test.ts +21 -0
  145. package/gs/go/token/index.ts +120 -0
  146. package/gs/os/file_unix_js.test.ts +103 -0
  147. package/gs/os/meta.json +1 -2
  148. package/gs/os/tempfile.gs.test.ts +85 -0
  149. package/gs/os/tempfile.gs.ts +71 -11
  150. package/gs/os/types_js.gs.ts +74 -153
  151. package/gs/reflect/index.ts +1 -1
  152. package/gs/reflect/type.ts +106 -17
  153. package/gs/reflect/typefor.test.ts +75 -0
  154. package/gs/sync/sync.test.ts +24 -0
  155. package/gs/sync/sync.ts +12 -0
  156. package/package.json +13 -13
  157. package/compiler/analysis.go +0 -3475
  158. package/compiler/analysis_test.go +0 -338
  159. package/compiler/assignment.go +0 -580
  160. package/compiler/builtin_test.go +0 -92
  161. package/compiler/code-writer.go +0 -115
  162. package/compiler/compiler_test.go +0 -149
  163. package/compiler/composite-lit.go +0 -779
  164. package/compiler/config_test.go +0 -62
  165. package/compiler/constraint.go +0 -86
  166. package/compiler/decl.go +0 -801
  167. package/compiler/expr-call-async.go +0 -188
  168. package/compiler/expr-call-builtins.go +0 -208
  169. package/compiler/expr-call-helpers.go +0 -382
  170. package/compiler/expr-call-make.go +0 -318
  171. package/compiler/expr-call-type-conversion.go +0 -520
  172. package/compiler/expr-call.go +0 -413
  173. package/compiler/expr-selector.go +0 -343
  174. package/compiler/expr-star.go +0 -82
  175. package/compiler/expr-type.go +0 -442
  176. package/compiler/expr-value.go +0 -89
  177. package/compiler/expr.go +0 -773
  178. package/compiler/field.go +0 -183
  179. package/compiler/gs_dependencies_test.go +0 -298
  180. package/compiler/lit.go +0 -322
  181. package/compiler/output.go +0 -72
  182. package/compiler/primitive.go +0 -149
  183. package/compiler/protobuf.go +0 -697
  184. package/compiler/sanitize.go +0 -100
  185. package/compiler/spec-struct.go +0 -995
  186. package/compiler/spec-value.go +0 -540
  187. package/compiler/spec.go +0 -725
  188. package/compiler/stmt-assign.go +0 -664
  189. package/compiler/stmt-for.go +0 -266
  190. package/compiler/stmt-range.go +0 -475
  191. package/compiler/stmt-select.go +0 -262
  192. package/compiler/stmt-type-switch.go +0 -147
  193. package/compiler/stmt.go +0 -1308
  194. package/compiler/type-assert.go +0 -386
  195. package/compiler/type-info.go +0 -156
  196. package/compiler/type-utils.go +0 -207
  197. package/compiler/type.go +0 -892
@@ -0,0 +1,1145 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+ "testing"
10
+ )
11
+
12
+ func TestConfigValidate(t *testing.T) {
13
+ tests := []struct {
14
+ name string
15
+ config *Config
16
+ wantErr bool
17
+ errMsg string
18
+ }{
19
+ {
20
+ name: "valid config",
21
+ config: &Config{
22
+ Dir: "/some/dir",
23
+ OutputPath: "/output/path",
24
+ BuildFlags: []string{"-tags", "sometag"},
25
+ },
26
+ },
27
+ {
28
+ name: "empty output path root",
29
+ config: &Config{
30
+ Dir: "/some/dir",
31
+ BuildFlags: []string{"-tags", "sometag"},
32
+ },
33
+ },
34
+ {
35
+ name: "nil config",
36
+ config: nil,
37
+ wantErr: true,
38
+ errMsg: "config cannot be nil",
39
+ },
40
+ {
41
+ name: "nil fset gets initialized",
42
+ config: &Config{
43
+ OutputPath: "/output/path",
44
+ },
45
+ },
46
+ }
47
+
48
+ for _, tt := range tests {
49
+ t.Run(tt.name, func(t *testing.T) {
50
+ err := tt.config.Validate()
51
+ if (err != nil) != tt.wantErr {
52
+ t.Fatalf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr)
53
+ }
54
+ if err != nil && err.Error() != tt.errMsg {
55
+ t.Fatalf("Config.Validate() error = %q, want %q", err.Error(), tt.errMsg)
56
+ }
57
+ if err == nil && tt.config.fset == nil {
58
+ t.Fatalf("Config.Validate() did not initialize fset")
59
+ }
60
+ })
61
+ }
62
+ }
63
+
64
+ func TestCompilePackagesRejectsSingleFileBeforeOutput(t *testing.T) {
65
+ outputDir := filepath.Join(t.TempDir(), "output")
66
+ comp, err := NewCompiler(&Config{OutputPath: outputDir}, nil, nil)
67
+ if err != nil {
68
+ t.Fatal(err.Error())
69
+ }
70
+
71
+ result, err := comp.CompilePackages(context.Background(), "main.go")
72
+ if err == nil {
73
+ t.Fatal("expected single-file request to fail")
74
+ }
75
+ requireDiagnostic(t, err, "goscript/request:single-file-unsupported")
76
+ if result == nil || len(result.Diagnostics) == 0 {
77
+ t.Fatalf("expected structured diagnostics in result")
78
+ }
79
+ if _, statErr := os.Stat(outputDir); !os.IsNotExist(statErr) {
80
+ t.Fatalf("compile wrote output directory before validation stopped: %v", statErr)
81
+ }
82
+ }
83
+
84
+ func TestCompilePackagesEmitsSimplePackage(t *testing.T) {
85
+ moduleDir := writePackageGraphFixture(t, map[string]string{
86
+ "go.mod": "module example.test/simple\n\ngo 1.25.3\n",
87
+ "main.go": strings.Join([]string{
88
+ "package main",
89
+ "const Greeting = \"Hello\"",
90
+ "func Add(a int, b int) int {",
91
+ " return a + b",
92
+ "}",
93
+ "func main() {",
94
+ " total := Add(2, 3)",
95
+ " size := len(Greeting)",
96
+ " print(\"total:\", size)",
97
+ " if total == 5 {",
98
+ " println(Greeting, total)",
99
+ " }",
100
+ " if false {",
101
+ " panic(\"unreachable\")",
102
+ " }",
103
+ "}",
104
+ "",
105
+ }, "\n"),
106
+ })
107
+ outputDir := filepath.Join(t.TempDir(), "output")
108
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
109
+ if err != nil {
110
+ t.Fatal(err.Error())
111
+ }
112
+
113
+ result, err := comp.CompilePackages(context.Background(), ".")
114
+ if err != nil {
115
+ t.Fatal(err.Error())
116
+ }
117
+ if result == nil || len(result.CompiledPackages) != 1 || result.CompiledPackages[0] != "example.test/simple" {
118
+ t.Fatalf("unexpected result: %#v", result)
119
+ }
120
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "simple", "main.gs.ts")
121
+ content, err := os.ReadFile(outputFile)
122
+ if err != nil {
123
+ t.Fatal(err.Error())
124
+ }
125
+ text := string(content)
126
+ for _, want := range []string{
127
+ "import * as $ from \"@goscript/builtin/index.js\"",
128
+ "export const Greeting: string = \"Hello\"",
129
+ "export function Add(a: number, b: number): number",
130
+ "export async function main(): Promise<void>",
131
+ "let size = $.len(Greeting)",
132
+ "$.print(\"total:\", size)",
133
+ "$.println(Greeting, total)",
134
+ "$.panic(\"unreachable\")",
135
+ "await main()",
136
+ } {
137
+ if !strings.Contains(text, want) {
138
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
139
+ }
140
+ }
141
+ }
142
+
143
+ func TestCompilePackagesEmitsPackageLocalImport(t *testing.T) {
144
+ moduleDir := writePackageGraphFixture(t, map[string]string{
145
+ "go.mod": "module example.test/imports\n\ngo 1.25.3\n",
146
+ "main.go": strings.Join([]string{
147
+ "package main",
148
+ "import \"example.test/imports/subpkg\"",
149
+ "func main() {",
150
+ " var b subpkg.Builder",
151
+ " b.Set(\"built\")",
152
+ " println(b.Value)",
153
+ " println(subpkg.Greet(\"world\"))",
154
+ " println(localMessage())",
155
+ "}",
156
+ "",
157
+ }, "\n"),
158
+ "helper.go": strings.Join([]string{
159
+ "package main",
160
+ "func localMessage() string {",
161
+ " return \"from helper\"",
162
+ "}",
163
+ "",
164
+ }, "\n"),
165
+ "subpkg/subpkg.go": strings.Join([]string{
166
+ "package subpkg",
167
+ "type Builder struct {",
168
+ " Value string",
169
+ "}",
170
+ "func (b *Builder) Set(value string) {",
171
+ " b.Value = value",
172
+ "}",
173
+ "func Greet(name string) string {",
174
+ " return \"Hello, \" + name",
175
+ "}",
176
+ "",
177
+ }, "\n"),
178
+ })
179
+ outputDir := filepath.Join(t.TempDir(), "output")
180
+ comp, err := NewCompiler(&Config{
181
+ Dir: moduleDir,
182
+ OutputPath: outputDir,
183
+ AllDependencies: true,
184
+ }, nil, nil)
185
+ if err != nil {
186
+ t.Fatal(err.Error())
187
+ }
188
+
189
+ result, err := comp.CompilePackages(context.Background(), ".")
190
+ if err != nil {
191
+ t.Fatal(err.Error())
192
+ }
193
+ if result == nil || len(result.CompiledPackages) != 2 {
194
+ t.Fatalf("unexpected result: %#v", result)
195
+ }
196
+ mainFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "main.gs.ts")
197
+ mainContent, err := os.ReadFile(mainFile)
198
+ if err != nil {
199
+ t.Fatal(err.Error())
200
+ }
201
+ if !strings.Contains(string(mainContent), "import * as subpkg from \"@goscript/example.test/imports/subpkg/index.js\"") {
202
+ t.Fatalf("missing package-local import:\n%s", string(mainContent))
203
+ }
204
+ if !strings.Contains(string(mainContent), "let b: $.VarRef<subpkg.Builder> = $.varRef($.markAsStructValue(new subpkg.Builder()))") {
205
+ t.Fatalf("missing imported struct zero value qualification:\n%s", string(mainContent))
206
+ }
207
+ if !strings.Contains(string(mainContent), "import * as __goscript_helper from \"./helper.gs.ts\"") ||
208
+ !strings.Contains(string(mainContent), "$.println(__goscript_helper.localMessage())") {
209
+ t.Fatalf("missing same-package helper import:\n%s", string(mainContent))
210
+ }
211
+ indexFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "subpkg", "index.ts")
212
+ indexContent, err := os.ReadFile(indexFile)
213
+ if err != nil {
214
+ t.Fatal(err.Error())
215
+ }
216
+ if string(indexContent) != "export { Builder, Greet } from \"./subpkg.gs.ts\"\n" {
217
+ t.Fatalf("unexpected subpkg index:\n%s", string(indexContent))
218
+ }
219
+ mainIndexFile := filepath.Join(outputDir, "@goscript", "example.test", "imports", "index.ts")
220
+ mainIndexContent, err := os.ReadFile(mainIndexFile)
221
+ if err != nil {
222
+ t.Fatal(err.Error())
223
+ }
224
+ if strings.Contains(string(mainIndexContent), "localMessage") {
225
+ t.Fatalf("unexported helper leaked into package index:\n%s", string(mainIndexContent))
226
+ }
227
+ }
228
+
229
+ func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
230
+ moduleDir := writePackageGraphFixture(t, map[string]string{
231
+ "go.mod": "module example.test/structs\n\ngo 1.25.3\n",
232
+ "main.go": strings.Join([]string{
233
+ "package main",
234
+ "type Counter struct {",
235
+ " // Value counts reads.",
236
+ " Value int `json:\"value\"`",
237
+ "}",
238
+ "func (c Counter) Read() int {",
239
+ " return c.Value",
240
+ "}",
241
+ "func (c *Counter) Set(v int) {",
242
+ " c.Value = v",
243
+ "}",
244
+ "func main() {",
245
+ " original := Counter{Value: 1}",
246
+ "",
247
+ " // Copy should stay readable in generated output.",
248
+ " copy := original",
249
+ " pointer := &original",
250
+ " pointer.Set(2)",
251
+ " var iface any = pointer",
252
+ " _, ok := iface.(*Counter)",
253
+ " println(copy.Read(), original.Read(), ok)",
254
+ "}",
255
+ "",
256
+ }, "\n"),
257
+ })
258
+ outputDir := filepath.Join(t.TempDir(), "output")
259
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
260
+ if err != nil {
261
+ t.Fatal(err.Error())
262
+ }
263
+
264
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
265
+ t.Fatal(err.Error())
266
+ }
267
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "structs", "main.gs.ts")
268
+ content, err := os.ReadFile(outputFile)
269
+ if err != nil {
270
+ t.Fatal(err.Error())
271
+ }
272
+ text := string(content)
273
+ for _, want := range []string{
274
+ "export class Counter",
275
+ "// Value counts reads.\n\tpublic get Value(): number",
276
+ "public clone(): Counter",
277
+ "public Read(): number",
278
+ "public Set(v: number): void",
279
+ "let original = $.varRef($.markAsStructValue(new Counter({Value: 1})))",
280
+ "let original = $.varRef($.markAsStructValue(new Counter({Value: 1})))\n\n\t// Copy should stay readable in generated output.\n\tlet copy",
281
+ "let copy = $.markAsStructValue(original.value.clone())",
282
+ "let pointer = original",
283
+ "$.pointerValue(pointer).Set(2)",
284
+ "let [, ok] = $.typeAssertTuple<Counter | $.VarRef<Counter> | null>(iface, { kind: $.TypeKind.Pointer, elemType: \"main.Counter\" })",
285
+ "\"Value\": { type: { kind: $.TypeKind.Basic, name: \"int\" }, tag: \"json:\\\"value\\\"\" }",
286
+ } {
287
+ if !strings.Contains(text, want) {
288
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
289
+ }
290
+ }
291
+ }
292
+
293
+ func TestCompilePackagesEmitsNestedPointerStorageAssertions(t *testing.T) {
294
+ moduleDir := writePackageGraphFixture(t, map[string]string{
295
+ "go.mod": "module example.test/pointers\n\ngo 1.25.3\n",
296
+ "main.go": strings.Join([]string{
297
+ "package main",
298
+ "func main() {",
299
+ " var x int = 10",
300
+ " p1 := &x",
301
+ " p2 := &p1",
302
+ " p3 := &p2",
303
+ " ***p3 = 12",
304
+ " println(x)",
305
+ "}",
306
+ "",
307
+ }, "\n"),
308
+ })
309
+ outputDir := filepath.Join(t.TempDir(), "output")
310
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
311
+ if err != nil {
312
+ t.Fatal(err.Error())
313
+ }
314
+
315
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
316
+ t.Fatal(err.Error())
317
+ }
318
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "pointers", "main.gs.ts")
319
+ content, err := os.ReadFile(outputFile)
320
+ if err != nil {
321
+ t.Fatal(err.Error())
322
+ }
323
+ text := string(content)
324
+ for _, want := range []string{
325
+ "let x: $.VarRef<number> = $.varRef(10)",
326
+ "let p1 = $.varRef(x)",
327
+ "let p2 = $.varRef(p1)",
328
+ "let p3 = p2",
329
+ "$.pointerValue($.pointerValue(p3))!.value = 12",
330
+ } {
331
+ if !strings.Contains(text, want) {
332
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
333
+ }
334
+ }
335
+ }
336
+
337
+ func TestCompilePackagesEmitsArraySliceMapStringAndNamedMethods(t *testing.T) {
338
+ moduleDir := writePackageGraphFixture(t, map[string]string{
339
+ "go.mod": "module example.test/collections\n\ngo 1.25.3\n",
340
+ "main.go": strings.Join([]string{
341
+ "package main",
342
+ "type MyInt int",
343
+ "func (m MyInt) Double() int { return int(m) * 2 }",
344
+ "type MySlice []int",
345
+ "func (s *MySlice) Add(v int) { *s = append(*s, v) }",
346
+ "func main() {",
347
+ " arr := [3]int{1: 10}",
348
+ " slice := make([]int, 0, 2)",
349
+ " empty := []rune{}",
350
+ " literal := []int{1, 2}",
351
+ " literal = append(literal, 3)",
352
+ " slice = append(slice, 5)",
353
+ " slice[0] = arr[1]",
354
+ " m := make(map[string]int)",
355
+ " m[\"one\"] = 1",
356
+ " value, ok := m[\"missing\"]",
357
+ " text := \"hé\"",
358
+ " var list MySlice",
359
+ " list.Add(7)",
360
+ " println(arr[1], slice[0], literal[2], len(slice), cap(slice), len(empty), value, ok, text[0], text[1], MyInt(5).Double(), len(list))",
361
+ "}",
362
+ "",
363
+ }, "\n"),
364
+ })
365
+ outputDir := filepath.Join(t.TempDir(), "output")
366
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
367
+ if err != nil {
368
+ t.Fatal(err.Error())
369
+ }
370
+
371
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
372
+ t.Fatal(err.Error())
373
+ }
374
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "collections", "main.gs.ts")
375
+ content, err := os.ReadFile(outputFile)
376
+ if err != nil {
377
+ t.Fatal(err.Error())
378
+ }
379
+ text := string(content)
380
+ for _, want := range []string{
381
+ "export type MyInt = number",
382
+ "export function MyInt_Double(m: MyInt): number",
383
+ "export type MySlice = $.Slice<number>",
384
+ "export function MySlice_Add(s: $.VarRef<MySlice>, v: number): void",
385
+ "let arr = [0, 10, 0]",
386
+ "let slice = $.makeSlice<number>(0, 2, \"number\")",
387
+ "let empty = $.arrayToSlice<number>([])",
388
+ "let literal = $.arrayToSlice<number>([1, 2])",
389
+ "literal = $.append(literal, 3)",
390
+ "slice![0] = arr[1]",
391
+ "let m = $.makeMap<string, number>()",
392
+ "$.mapSet(m, \"one\", 1)",
393
+ "let [value, ok] = $.mapGet(m, \"missing\", 0)",
394
+ "slice![0]",
395
+ "literal![2]",
396
+ "let list: $.VarRef<MySlice> = $.varRef(null)",
397
+ "MySlice_Add(list, 7)",
398
+ "$.indexStringOrBytes(text, 0)",
399
+ "MyInt_Double(5)",
400
+ } {
401
+ if !strings.Contains(text, want) {
402
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
403
+ }
404
+ }
405
+ }
406
+
407
+ func TestCompilePackagesEmitsInterfacesMethodValuesTypeSwitchesAndFunctionAssertions(t *testing.T) {
408
+ moduleDir := writePackageGraphFixture(t, map[string]string{
409
+ "go.mod": "module example.test/interfaces\n\ngo 1.25.3\n",
410
+ "main.go": strings.Join([]string{
411
+ "package main",
412
+ "type Greeter func(name string) string",
413
+ "func greet(name string) string { return \"hello \" + name }",
414
+ "type Reader interface { Read() string }",
415
+ "type Closer interface { Close() string }",
416
+ "type ReadCloser interface { Reader; Closer }",
417
+ "type Counter struct { Value int }",
418
+ "func (c *Counter) Inc() { c.Value++ }",
419
+ "func (c Counter) Read() string { return \"read\" }",
420
+ "func (c Counter) Close() string { return \"close\" }",
421
+ "func call(fn func()) { fn() }",
422
+ "func main() {",
423
+ " counter := &Counter{}",
424
+ " call(counter.Inc)",
425
+ " var rc ReadCloser = counter",
426
+ " _, ok := rc.(ReadCloser)",
427
+ " var i any = Greeter(greet)",
428
+ " fn, ok := i.(Greeter)",
429
+ " var l any = (*struct { Name string })(nil)",
430
+ " _, ok2 := l.(*struct { Name string })",
431
+ " switch v := rc.(type) {",
432
+ " case ReadCloser:",
433
+ " println(v.Read(), fn(\"gopher\"), ok, ok2)",
434
+ " }",
435
+ "}",
436
+ "",
437
+ }, "\n"),
438
+ })
439
+ outputDir := filepath.Join(t.TempDir(), "output")
440
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
441
+ if err != nil {
442
+ t.Fatal(err.Error())
443
+ }
444
+
445
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
446
+ t.Fatal(err.Error())
447
+ }
448
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "interfaces", "main.gs.ts")
449
+ content, err := os.ReadFile(outputFile)
450
+ if err != nil {
451
+ t.Fatal(err.Error())
452
+ }
453
+ text := string(content)
454
+ for _, want := range []string{
455
+ "export type Greeter = ((name: string) => string) | null",
456
+ "export type ReadCloser = null | {",
457
+ "Read(): string",
458
+ "Close(): string",
459
+ "$.registerInterfaceType(\n\t\"main.ReadCloser\"",
460
+ "((__receiver) => () => __receiver.Inc())($.pointerValue(counter))",
461
+ "$.namedFunction(greet, \"main.Greeter\")",
462
+ "$.typedNil(\"*struct{Name string}\")",
463
+ "elemType: { kind: $.TypeKind.Struct, methods: [], fields: {\"Name\": { kind: $.TypeKind.Basic, name: \"string\" }} }",
464
+ "let fn = __goscriptTuple",
465
+ "$.typeSwitch(",
466
+ "types: [\"main.ReadCloser\"]",
467
+ } {
468
+ if !strings.Contains(text, want) {
469
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
470
+ }
471
+ }
472
+ }
473
+
474
+ func TestCompilePackagesAssertsInterfaceMethodReceivers(t *testing.T) {
475
+ moduleDir := writePackageGraphFixture(t, map[string]string{
476
+ "go.mod": "module example.test/interface-receivers\n\ngo 1.25.3\n",
477
+ "main.go": strings.Join([]string{
478
+ "package main",
479
+ "type FileInfo interface { Name() string }",
480
+ "type fileInfo struct { name string }",
481
+ "func (f fileInfo) Name() string { return f.name }",
482
+ "func stat() (FileInfo, error) { return fileInfo{name: \"demo\"}, nil }",
483
+ "func main() {",
484
+ " info, err := stat()",
485
+ " if err == nil {",
486
+ " println(info.Name())",
487
+ " }",
488
+ "}",
489
+ "",
490
+ }, "\n"),
491
+ })
492
+ outputDir := filepath.Join(t.TempDir(), "output")
493
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
494
+ if err != nil {
495
+ t.Fatal(err.Error())
496
+ }
497
+
498
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
499
+ t.Fatal(err.Error())
500
+ }
501
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "interface-receivers", "main.gs.ts")
502
+ content, err := os.ReadFile(outputFile)
503
+ if err != nil {
504
+ t.Fatal(err.Error())
505
+ }
506
+ text := string(content)
507
+ for _, want := range []string{
508
+ "export type FileInfo = null | {",
509
+ "$.println(info!.Name())",
510
+ } {
511
+ if !strings.Contains(text, want) {
512
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
513
+ }
514
+ }
515
+ }
516
+
517
+ func TestCompilePackagesBoxesTypedNilInterfaceValues(t *testing.T) {
518
+ moduleDir := writePackageGraphFixture(t, map[string]string{
519
+ "go.mod": "module example.test/typed-nil-interface\n\ngo 1.25.3\n",
520
+ "main.go": strings.Join([]string{
521
+ "package main",
522
+ "type Animal interface { Name() string }",
523
+ "type Dog struct { name string }",
524
+ "func (d *Dog) Name() string {",
525
+ " if d == nil {",
526
+ " return \"unknown dog\"",
527
+ " }",
528
+ " return d.name",
529
+ "}",
530
+ "func FindDog() *Dog { return nil }",
531
+ "func FindAnimal() Animal { return Animal(FindDog()) }",
532
+ "func main() {",
533
+ " animal := FindAnimal()",
534
+ " println(animal.Name())",
535
+ " var dog *Dog = nil",
536
+ " var a Animal = dog",
537
+ " println(a == nil)",
538
+ "}",
539
+ "",
540
+ }, "\n"),
541
+ })
542
+ outputDir := filepath.Join(t.TempDir(), "output")
543
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
544
+ if err != nil {
545
+ t.Fatal(err.Error())
546
+ }
547
+
548
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
549
+ t.Fatal(err.Error())
550
+ }
551
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "typed-nil-interface", "main.gs.ts")
552
+ content, err := os.ReadFile(outputFile)
553
+ if err != nil {
554
+ t.Fatal(err.Error())
555
+ }
556
+ text := string(content)
557
+ for _, want := range []string{
558
+ "return $.interfaceValue<Animal>(FindDog(), \"*main.Dog\")",
559
+ "$.println(animal!.Name())",
560
+ "let a: Animal = $.interfaceValue<Animal>(dog, \"*main.Dog\")",
561
+ } {
562
+ if !strings.Contains(text, want) {
563
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
564
+ }
565
+ }
566
+ }
567
+
568
+ func TestCompilePackagesEmitsGenericMethodsAliasesAndDictionaries(t *testing.T) {
569
+ moduleDir := writePackageGraphFixture(t, map[string]string{
570
+ "go.mod": "module example.test/generics\n\ngo 1.25.3\n",
571
+ "main.go": strings.Join([]string{
572
+ "package main",
573
+ "type Stringer interface { String() string }",
574
+ "type MyInt int",
575
+ "func (m MyInt) String() string { return \"int\" }",
576
+ "type Box[T any] struct { value T }",
577
+ "func (b Box[T]) Get() T { return b.value }",
578
+ "func NewBox[T any](value T) Box[T] { return Box[T]{value: value} }",
579
+ "type Set[T comparable] map[T]struct{}",
580
+ "func ZeroValue[T Stringer]() T {",
581
+ " var zero T",
582
+ " return zero",
583
+ "}",
584
+ "func CallString[T Stringer](v T) string { return v.String() }",
585
+ "func Sum[T Stringer](vals ...T) T {",
586
+ " var zero T",
587
+ " return zero",
588
+ "}",
589
+ "func main() {",
590
+ " box := NewBox(7)",
591
+ " println(box.Get())",
592
+ " seen := make(Set[int])",
593
+ " seen[1] = struct{}{}",
594
+ " zero := ZeroValue[MyInt]()",
595
+ " println(CallString(zero))",
596
+ " sum := Sum[MyInt]()",
597
+ " println(CallString(sum))",
598
+ "}",
599
+ "",
600
+ }, "\n"),
601
+ })
602
+ outputDir := filepath.Join(t.TempDir(), "output")
603
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
604
+ if err != nil {
605
+ t.Fatal(err.Error())
606
+ }
607
+
608
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
609
+ t.Fatal(err.Error())
610
+ }
611
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "generics", "main.gs.ts")
612
+ content, err := os.ReadFile(outputFile)
613
+ if err != nil {
614
+ t.Fatal(err.Error())
615
+ }
616
+ text := string(content)
617
+ for _, want := range []string{
618
+ "public Get(): any",
619
+ "export function NewBox(__typeArgs: $.GenericTypeArgs | undefined, value: any): Box",
620
+ "let seen = $.makeMap<number, Record<string, unknown>>()",
621
+ "$.mapSet(seen, 1, {})",
622
+ "$.genericZero(__typeArgs, \"T\", null)",
623
+ "$.callGenericMethod(__typeArgs, \"T\", \"String\", v)",
624
+ "ZeroValue({T: { type: \"main.MyInt\", zero: () => 0, methods: {String: MyInt_String} }})",
625
+ "CallString({T: { type: \"main.MyInt\", zero: () => 0, methods: {String: MyInt_String} }}, zero)",
626
+ "Sum({T: { type: \"main.MyInt\", zero: () => 0, methods: {String: MyInt_String} }}, null)",
627
+ } {
628
+ if !strings.Contains(text, want) {
629
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
630
+ }
631
+ }
632
+ }
633
+
634
+ func TestCompilePackagesAttachesFunctionLiteralTypeInfo(t *testing.T) {
635
+ moduleDir := writePackageGraphFixture(t, map[string]string{
636
+ "go.mod": "module example.test/function-type-info\n\ngo 1.25.3\n",
637
+ "main.go": strings.Join([]string{
638
+ "package main",
639
+ "type Callback func(value int) string",
640
+ "func call(cb Callback) string {",
641
+ " return cb(1)",
642
+ "}",
643
+ "func main() {",
644
+ " fn := func(value int) string {",
645
+ " return \"\"",
646
+ " }",
647
+ " var cb Callback = nil",
648
+ " _ = fn",
649
+ " _ = cb",
650
+ " _ = call(fn)",
651
+ "}",
652
+ "",
653
+ }, "\n"),
654
+ })
655
+ outputDir := filepath.Join(t.TempDir(), "output")
656
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
657
+ if err != nil {
658
+ t.Fatal(err.Error())
659
+ }
660
+
661
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
662
+ t.Fatal(err.Error())
663
+ }
664
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "function-type-info", "main.gs.ts")
665
+ content, err := os.ReadFile(outputFile)
666
+ if err != nil {
667
+ t.Fatal(err.Error())
668
+ }
669
+ text := string(content)
670
+ for _, want := range []string{
671
+ "export type Callback = ((value: number) => string) | null",
672
+ "export function call(cb: Callback): string {\n\treturn cb!(1)",
673
+ "$.functionValue((value: number): string => {",
674
+ "kind: $.TypeKind.Function",
675
+ "params: [{ kind: $.TypeKind.Basic, name: \"int\" }]",
676
+ "results: [{ kind: $.TypeKind.Basic, name: \"string\" }]",
677
+ } {
678
+ if !strings.Contains(text, want) {
679
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
680
+ }
681
+ }
682
+ }
683
+
684
+ func TestCompilePackagesPacksVariadicCalls(t *testing.T) {
685
+ moduleDir := writePackageGraphFixture(t, map[string]string{
686
+ "go.mod": "module example.test/variadic\n\ngo 1.25.3\n",
687
+ "main.go": strings.Join([]string{
688
+ "package main",
689
+ "type Collector func(label string, parts ...string) string",
690
+ "type Joiner interface {",
691
+ " Join(parts ...string) string",
692
+ "}",
693
+ "type Path struct{}",
694
+ "func collect(label string, parts ...string) string {",
695
+ " for _, part := range parts {",
696
+ " if part == \"\" {",
697
+ " return label",
698
+ " }",
699
+ " }",
700
+ " return label + string(rune(len(parts)+'0'))",
701
+ "}",
702
+ "func maybeErr(parts ...string) error { return nil }",
703
+ "func (Path) Join(parts ...string) string {",
704
+ " return collect(\"method\", parts...)",
705
+ "}",
706
+ "func main() {",
707
+ " parts := []string{\"a\", \"b\"}",
708
+ " collect(\"none\")",
709
+ " collect(\"two\", \"a\", \"b\")",
710
+ " collect(\"spread\", parts...)",
711
+ " parts = append(parts, \"c\", \"d\")",
712
+ " maybeErr(\"ok\")",
713
+ " var fn Collector = collect",
714
+ " fn(\"fn\", \"x\")",
715
+ " var joiner Joiner = Path{}",
716
+ " joiner.Join(\"q\", \"r\")",
717
+ "}",
718
+ "",
719
+ }, "\n"),
720
+ })
721
+ outputDir := filepath.Join(t.TempDir(), "output")
722
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
723
+ if err != nil {
724
+ t.Fatal(err.Error())
725
+ }
726
+
727
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
728
+ t.Fatal(err.Error())
729
+ }
730
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "variadic", "main.gs.ts")
731
+ content, err := os.ReadFile(outputFile)
732
+ if err != nil {
733
+ t.Fatal(err.Error())
734
+ }
735
+ text := string(content)
736
+ for _, want := range []string{
737
+ "export type Collector = ((label: string, parts: $.Slice<string>) => string) | null",
738
+ "Join(parts: $.Slice<string>): string",
739
+ "export function collect(label: string, parts: $.Slice<string>): string",
740
+ "let part = parts![__rangeIndex]",
741
+ "export function maybeErr(parts: $.Slice<string>): $.GoError",
742
+ "public Join(parts: $.Slice<string>): string",
743
+ "collect(\"none\", null)",
744
+ "collect(\"two\", $.arrayToSlice<string>([\"a\", \"b\"]))",
745
+ "collect(\"spread\", parts)",
746
+ "$.append(parts, \"c\", \"d\")",
747
+ "maybeErr($.arrayToSlice<string>([\"ok\"]))",
748
+ "fn!(\"fn\", $.arrayToSlice<string>([\"x\"]))",
749
+ "joiner!.Join($.arrayToSlice<string>([\"q\", \"r\"]))",
750
+ } {
751
+ if !strings.Contains(text, want) {
752
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
753
+ }
754
+ }
755
+ }
756
+
757
+ func TestCompilePackagesLowersRangeOverFunctionIterators(t *testing.T) {
758
+ moduleDir := writePackageGraphFixture(t, map[string]string{
759
+ "go.mod": "module example.test/iterators\n\ngo 1.25.3\n",
760
+ "main.go": strings.Join([]string{
761
+ "package main",
762
+ "func pairs(yield func(int, string) bool) {",
763
+ " values := []string{\"a\", \"b\"}",
764
+ " for i, v := range values {",
765
+ " if !yield(i, v) {",
766
+ " break",
767
+ " }",
768
+ " }",
769
+ "}",
770
+ "func main() {",
771
+ " for i, v := range pairs {",
772
+ " println(i, v)",
773
+ " }",
774
+ " var last int",
775
+ " for last = range pairs {",
776
+ " println(last)",
777
+ " }",
778
+ " for i := range 3 {",
779
+ " if i == 1 {",
780
+ " continue",
781
+ " }",
782
+ " println(i)",
783
+ " }",
784
+ "}",
785
+ "",
786
+ }, "\n"),
787
+ })
788
+ outputDir := filepath.Join(t.TempDir(), "output")
789
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
790
+ if err != nil {
791
+ t.Fatal(err.Error())
792
+ }
793
+
794
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
795
+ t.Fatal(err.Error())
796
+ }
797
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "iterators", "main.gs.ts")
798
+ content, err := os.ReadFile(outputFile)
799
+ if err != nil {
800
+ t.Fatal(err.Error())
801
+ }
802
+ text := string(content)
803
+ for _, want := range []string{
804
+ "if (!_yield!(i, v))",
805
+ "break",
806
+ "pairs!((i, v) => {",
807
+ "last = __goscriptRange",
808
+ "return true",
809
+ "continue",
810
+ } {
811
+ if !strings.Contains(text, want) {
812
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
813
+ }
814
+ }
815
+ }
816
+
817
+ func TestCompilePackagesEmitsAsyncChannelsSelectAndDefer(t *testing.T) {
818
+ moduleDir := writePackageGraphFixture(t, map[string]string{
819
+ "go.mod": "module example.test/async\n\ngo 1.25.3\n",
820
+ "main.go": strings.Join([]string{
821
+ "package main",
822
+ "type Processor interface { Process(v int) int }",
823
+ "type Worker struct { ch chan int }",
824
+ "func (w *Worker) Process(v int) int {",
825
+ " w.ch <- v",
826
+ " return <-w.ch",
827
+ "}",
828
+ "func call(p Processor) int { return p.Process(2) }",
829
+ "func main() {",
830
+ " ch := make(chan int, 1)",
831
+ " defer func() { <-ch }()",
832
+ " go func() { ch <- 1 }()",
833
+ " select {",
834
+ " case v := <-ch:",
835
+ " println(v)",
836
+ " default:",
837
+ " println(\"default\")",
838
+ " }",
839
+ " _ = call(&Worker{ch: make(chan int, 1)})",
840
+ "}",
841
+ "",
842
+ }, "\n"),
843
+ })
844
+ outputDir := filepath.Join(t.TempDir(), "output")
845
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
846
+ if err != nil {
847
+ t.Fatal(err.Error())
848
+ }
849
+
850
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
851
+ t.Fatal(err.Error())
852
+ }
853
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "async", "main.gs.ts")
854
+ content, err := os.ReadFile(outputFile)
855
+ if err != nil {
856
+ t.Fatal(err.Error())
857
+ }
858
+ text := string(content)
859
+ for _, want := range []string{
860
+ "Process(v: number): Promise<number>",
861
+ "public async Process(v: number): Promise<number>",
862
+ "let ch = $.makeChannel<number>(1, 0, \"both\")",
863
+ "await $.chanSend($.pointerValue(w).ch, v)",
864
+ "return await $.chanRecv($.pointerValue(w).ch)",
865
+ "await using __defer = new $.AsyncDisposableStack()",
866
+ "queueMicrotask(async () => { await ($.functionValue(async (): Promise<void> => {",
867
+ "$.selectStatement<any, void>([",
868
+ "let v = result.value",
869
+ "await call(new Worker({ch: $.makeChannel<number>(1, 0, \"both\")}))",
870
+ } {
871
+ if !strings.Contains(text, want) {
872
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
873
+ }
874
+ }
875
+ }
876
+
877
+ func TestCompilePackagesScopesIfInitDeclarations(t *testing.T) {
878
+ moduleDir := writePackageGraphFixture(t, map[string]string{
879
+ "go.mod": "module example.test/ifinit\n\ngo 1.25.3\n",
880
+ "main.go": strings.Join([]string{
881
+ "package main",
882
+ "func pair() (string, bool) {",
883
+ " return \"value\", true",
884
+ "}",
885
+ "func main() {",
886
+ " if value, ok := pair(); ok {",
887
+ " println(value)",
888
+ " }",
889
+ " if value, ok := pair(); ok {",
890
+ " println(value)",
891
+ " }",
892
+ "}",
893
+ "",
894
+ }, "\n"),
895
+ })
896
+ outputDir := filepath.Join(t.TempDir(), "output")
897
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
898
+ if err != nil {
899
+ t.Fatal(err.Error())
900
+ }
901
+
902
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
903
+ t.Fatal(err.Error())
904
+ }
905
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "ifinit", "main.gs.ts"))
906
+ if err != nil {
907
+ t.Fatal(err.Error())
908
+ }
909
+ if strings.Count(string(content), "{\n\t\tlet [value, ok] = pair()") != 2 {
910
+ t.Fatalf("if init declarations were not block scoped:\n%s", string(content))
911
+ }
912
+ }
913
+
914
+ func TestCompilePackagesLowersSwitchesAndFunctionValueCalls(t *testing.T) {
915
+ moduleDir := writePackageGraphFixture(t, map[string]string{
916
+ "go.mod": "module example.test/switchcall\n\ngo 1.25.3\n",
917
+ "main.go": strings.Join([]string{
918
+ "package main",
919
+ "func main() {",
920
+ " value := 2",
921
+ " switch value {",
922
+ " case 1:",
923
+ " println(\"one\")",
924
+ " case 2, 3:",
925
+ " local := \"two-three\"",
926
+ " println(local)",
927
+ " default:",
928
+ " println(\"other\")",
929
+ " }",
930
+ " switch {",
931
+ " case value > 1:",
932
+ " println(\"positive\")",
933
+ " }",
934
+ " release := func() { println(\"release\") }",
935
+ " rel := &release",
936
+ " (*rel)()",
937
+ " wrapped := func() {",
938
+ " defer println(\"wrapped deferred\")",
939
+ " println(\"wrapped body\")",
940
+ " }",
941
+ " wrapped()",
942
+ "}",
943
+ "",
944
+ }, "\n"),
945
+ })
946
+ outputDir := filepath.Join(t.TempDir(), "output")
947
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
948
+ if err != nil {
949
+ t.Fatal(err.Error())
950
+ }
951
+
952
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
953
+ t.Fatal(err.Error())
954
+ }
955
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "switchcall", "main.gs.ts"))
956
+ if err != nil {
957
+ t.Fatal(err.Error())
958
+ }
959
+ text := string(content)
960
+ for _, want := range []string{
961
+ "switch (value) {",
962
+ "case 2:",
963
+ "case 3:",
964
+ "let local = \"two-three\"",
965
+ "switch (true) {",
966
+ "($.pointerValue(rel))!()",
967
+ "$.functionValue((): void => {\n\t\tusing __defer = new $.DisposableStack()",
968
+ "__defer.defer(() => { $.println(\"wrapped deferred\") })",
969
+ "$.println(\"wrapped body\")",
970
+ } {
971
+ if !strings.Contains(text, want) {
972
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
973
+ }
974
+ }
975
+ if strings.Count(text, "\t\tbreak\n") < 3 {
976
+ t.Fatalf("switch cases were not rendered with implicit breaks:\n%s", text)
977
+ }
978
+ }
979
+
980
+ func TestCompilePackagesLowersMethodValuesWithFixedParameters(t *testing.T) {
981
+ moduleDir := writePackageGraphFixture(t, map[string]string{
982
+ "go.mod": "module example.test/methodvalue\n\ngo 1.25.3\n",
983
+ "main.go": strings.Join([]string{
984
+ "package main",
985
+ "type Counter int",
986
+ "func (c Counter) Add(n int) int {",
987
+ " return int(c) + n",
988
+ "}",
989
+ "type Runner struct{}",
990
+ "func (r Runner) Run() {",
991
+ " println(\"run\")",
992
+ "}",
993
+ "func main() {",
994
+ " c := Counter(4)",
995
+ " add := c.Add",
996
+ " println(add(3))",
997
+ " r := Runner{}",
998
+ " run := r.Run",
999
+ " run()",
1000
+ "}",
1001
+ "",
1002
+ }, "\n"),
1003
+ })
1004
+ outputDir := filepath.Join(t.TempDir(), "output")
1005
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1006
+ if err != nil {
1007
+ t.Fatal(err.Error())
1008
+ }
1009
+
1010
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1011
+ t.Fatal(err.Error())
1012
+ }
1013
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "methodvalue", "main.gs.ts"))
1014
+ if err != nil {
1015
+ t.Fatal(err.Error())
1016
+ }
1017
+ text := string(content)
1018
+ for _, want := range []string{
1019
+ "((__receiver) => (n: number) => Counter_Add(__receiver, n))(c)",
1020
+ "((__receiver) => () => __receiver.Run())(",
1021
+ } {
1022
+ if !strings.Contains(text, want) {
1023
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1024
+ }
1025
+ }
1026
+ if strings.Contains(text, "...args: any[]") {
1027
+ t.Fatalf("method value lowering still uses spread args:\n%s", text)
1028
+ }
1029
+ }
1030
+
1031
+ func TestCompilePackagesQualifiesImportedTypesInSignaturesAndZeroValues(t *testing.T) {
1032
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1033
+ "go.mod": "module example.test/qualified\n\ngo 1.25.3\n",
1034
+ "lib/lib.go": strings.Join([]string{
1035
+ "package lib",
1036
+ "type Box struct {",
1037
+ " Value int",
1038
+ "}",
1039
+ "",
1040
+ }, "\n"),
1041
+ "main.go": strings.Join([]string{
1042
+ "package main",
1043
+ "import (",
1044
+ " \"example.test/qualified/lib\"",
1045
+ " \"sync/atomic\"",
1046
+ ")",
1047
+ "type Holder struct {",
1048
+ " Box lib.Box",
1049
+ " Boxes []lib.Box",
1050
+ " Fn func(lib.Box) (lib.Box, error)",
1051
+ " Ptr atomic.Pointer[func()]",
1052
+ "}",
1053
+ "func Use(fn func(lib.Box) (lib.Box, error), box lib.Box) (lib.Box, error) {",
1054
+ " return fn(box)",
1055
+ "}",
1056
+ "func main() {",
1057
+ " _ = Holder{}",
1058
+ " _, _ = Use(func(box lib.Box) (lib.Box, error) { return box, nil }, lib.Box{})",
1059
+ "}",
1060
+ "",
1061
+ }, "\n"),
1062
+ })
1063
+ outputDir := filepath.Join(t.TempDir(), "output")
1064
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1065
+ if err != nil {
1066
+ t.Fatal(err.Error())
1067
+ }
1068
+
1069
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1070
+ t.Fatal(err.Error())
1071
+ }
1072
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "qualified", "main.gs.ts"))
1073
+ if err != nil {
1074
+ t.Fatal(err.Error())
1075
+ }
1076
+ text := string(content)
1077
+ for _, want := range []string{
1078
+ "Box: $.VarRef<lib.Box>",
1079
+ "Boxes: $.VarRef<$.Slice<lib.Box>>",
1080
+ "Fn: $.VarRef<((_p0: lib.Box) => [lib.Box, $.GoError]) | null>",
1081
+ "Ptr: $.VarRef<atomic.Pointer<(() => void) | null>>",
1082
+ "$.markAsStructValue(new lib.Box())",
1083
+ "$.markAsStructValue(new atomic.Pointer<(() => void) | null>())",
1084
+ "export function Use(fn: ((_p0: lib.Box) => [lib.Box, $.GoError]) | null, box: lib.Box): [lib.Box, $.GoError]",
1085
+ "$.functionValue((box: lib.Box): [lib.Box, $.GoError] => {",
1086
+ } {
1087
+ if !strings.Contains(text, want) {
1088
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1089
+ }
1090
+ }
1091
+ }
1092
+
1093
+ func TestCompilePackagesReportsUnsupportedUnaryBeforeOutput(t *testing.T) {
1094
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1095
+ "go.mod": "module example.test/unsupported\n\ngo 1.25.3\n",
1096
+ "main.go": strings.Join([]string{
1097
+ "package main",
1098
+ "var value = 1",
1099
+ "func main() {",
1100
+ " println(^value)",
1101
+ "}",
1102
+ "",
1103
+ }, "\n"),
1104
+ })
1105
+ outputDir := filepath.Join(t.TempDir(), "output")
1106
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1107
+ if err != nil {
1108
+ t.Fatal(err.Error())
1109
+ }
1110
+
1111
+ result, err := comp.CompilePackages(context.Background(), ".")
1112
+ if err == nil {
1113
+ t.Fatal("expected unsupported unary expression to fail")
1114
+ }
1115
+ requireDiagnostic(t, err, "goscript/lowering:unsupported")
1116
+ if result == nil || len(result.Diagnostics) == 0 {
1117
+ t.Fatalf("expected structured diagnostics in result")
1118
+ }
1119
+ if _, statErr := os.Stat(outputDir); !os.IsNotExist(statErr) {
1120
+ t.Fatalf("compile wrote output directory after lowering failed: %v", statErr)
1121
+ }
1122
+ }
1123
+
1124
+ func TestCompileSourceToTypeScriptStopsAtSkeleton(t *testing.T) {
1125
+ _, err := CompileSourceToTypeScript("package main\nfunc main() {}\n", "main")
1126
+ if err == nil {
1127
+ t.Fatal("expected WASM source compile to fail in the skeleton")
1128
+ }
1129
+ requireDiagnostic(t, err, "goscript/wasm:single-file-unsupported")
1130
+ }
1131
+
1132
+ func requireDiagnostic(t *testing.T, err error, code string) {
1133
+ t.Helper()
1134
+
1135
+ var compileErr *CompileError
1136
+ if !errors.As(err, &compileErr) {
1137
+ t.Fatalf("expected CompileError, got %T: %v", err, err)
1138
+ }
1139
+ for _, diag := range compileErr.Diagnostics {
1140
+ if diag.Code == code {
1141
+ return
1142
+ }
1143
+ }
1144
+ t.Fatalf("missing diagnostic %q in %#v", code, compileErr.Diagnostics)
1145
+ }