goscript 0.1.2 → 0.1.4

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 (138) hide show
  1. package/cmd/goscript/cmd_compile.go +28 -8
  2. package/cmd/goscript/cmd_compile_test.go +105 -6
  3. package/compiler/build-flags.go +9 -10
  4. package/compiler/gotest/runner_test.go +127 -0
  5. package/compiler/lowered-program.go +1 -0
  6. package/compiler/lowering.go +1325 -194
  7. package/compiler/lowering_bench_test.go +350 -0
  8. package/compiler/override-registry_test.go +43 -0
  9. package/compiler/package-graph.go +61 -4
  10. package/compiler/package-graph_test.go +30 -0
  11. package/compiler/semantic-model-types.go +8 -0
  12. package/compiler/semantic-model.go +447 -22
  13. package/compiler/semantic-model_test.go +138 -0
  14. package/compiler/skeleton_test.go +1436 -50
  15. package/compiler/typescript-emitter.go +47 -4
  16. package/dist/gs/builtin/builtin.d.ts +2 -2
  17. package/dist/gs/builtin/builtin.js +20 -0
  18. package/dist/gs/builtin/builtin.js.map +1 -1
  19. package/dist/gs/builtin/channel.js +36 -9
  20. package/dist/gs/builtin/channel.js.map +1 -1
  21. package/dist/gs/builtin/slice.js +5 -0
  22. package/dist/gs/builtin/slice.js.map +1 -1
  23. package/dist/gs/builtin/type.d.ts +1 -1
  24. package/dist/gs/builtin/type.js +80 -8
  25. package/dist/gs/builtin/type.js.map +1 -1
  26. package/dist/gs/bytes/bytes.gs.d.ts +7 -5
  27. package/dist/gs/bytes/bytes.gs.js +10 -4
  28. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  29. package/dist/gs/compress/zlib/index.d.ts +3 -3
  30. package/dist/gs/compress/zlib/index.js +88 -26
  31. package/dist/gs/compress/zlib/index.js.map +1 -1
  32. package/dist/gs/crypto/sha1/index.d.ts +5 -0
  33. package/dist/gs/crypto/sha1/index.js +103 -0
  34. package/dist/gs/crypto/sha1/index.js.map +1 -0
  35. package/dist/gs/crypto/sha256/index.js +2 -5
  36. package/dist/gs/crypto/sha256/index.js.map +1 -1
  37. package/dist/gs/crypto/sha512/index.js +2 -5
  38. package/dist/gs/crypto/sha512/index.js.map +1 -1
  39. package/dist/gs/embed/index.d.ts +6 -0
  40. package/dist/gs/embed/index.js +210 -5
  41. package/dist/gs/embed/index.js.map +1 -1
  42. package/dist/gs/fmt/fmt.d.ts +4 -4
  43. package/dist/gs/fmt/fmt.js +93 -19
  44. package/dist/gs/fmt/fmt.js.map +1 -1
  45. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js +118 -6
  46. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js.map +1 -1
  47. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.d.ts +45 -0
  48. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js +229 -0
  49. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js.map +1 -0
  50. package/dist/gs/io/fs/readdir.js +5 -3
  51. package/dist/gs/io/fs/readdir.js.map +1 -1
  52. package/dist/gs/io/io.d.ts +18 -11
  53. package/dist/gs/io/io.js +107 -44
  54. package/dist/gs/io/io.js.map +1 -1
  55. package/dist/gs/math/bits/index.d.ts +26 -5
  56. package/dist/gs/math/bits/index.js +13 -24
  57. package/dist/gs/math/bits/index.js.map +1 -1
  58. package/dist/gs/net/http/httptest/index.js +7 -5
  59. package/dist/gs/net/http/httptest/index.js.map +1 -1
  60. package/dist/gs/net/http/index.d.ts +11 -1
  61. package/dist/gs/net/http/index.js +157 -11
  62. package/dist/gs/net/http/index.js.map +1 -1
  63. package/dist/gs/os/types_js.gs.d.ts +6 -2
  64. package/dist/gs/os/types_js.gs.js +169 -8
  65. package/dist/gs/os/types_js.gs.js.map +1 -1
  66. package/dist/gs/os/zero_copy_posix.gs.js +1 -1
  67. package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
  68. package/dist/gs/reflect/type.d.ts +1 -0
  69. package/dist/gs/reflect/type.js +80 -51
  70. package/dist/gs/reflect/type.js.map +1 -1
  71. package/dist/gs/strings/reader.d.ts +1 -1
  72. package/dist/gs/strings/reader.js +2 -2
  73. package/dist/gs/strings/reader.js.map +1 -1
  74. package/dist/gs/sync/sync.d.ts +2 -1
  75. package/dist/gs/sync/sync.js +37 -16
  76. package/dist/gs/sync/sync.js.map +1 -1
  77. package/dist/gs/syscall/js/index.js +9 -0
  78. package/dist/gs/syscall/js/index.js.map +1 -1
  79. package/dist/gs/testing/testing.js +8 -6
  80. package/dist/gs/testing/testing.js.map +1 -1
  81. package/gs/builtin/builtin.ts +25 -2
  82. package/gs/builtin/channel.ts +47 -9
  83. package/gs/builtin/runtime-contract.test.ts +78 -0
  84. package/gs/builtin/slice.ts +7 -0
  85. package/gs/builtin/type.ts +97 -8
  86. package/gs/bytes/bytes.gs.ts +19 -10
  87. package/gs/bytes/bytes.test.ts +17 -0
  88. package/gs/compress/zlib/index.test.ts +97 -0
  89. package/gs/compress/zlib/index.ts +117 -27
  90. package/gs/compress/zlib/meta.json +4 -1
  91. package/gs/context/context.test.ts +5 -1
  92. package/gs/crypto/sha1/index.test.ts +45 -0
  93. package/gs/crypto/sha1/index.ts +127 -0
  94. package/gs/crypto/sha1/meta.json +8 -0
  95. package/gs/crypto/sha256/index.test.ts +14 -2
  96. package/gs/crypto/sha256/index.ts +3 -6
  97. package/gs/crypto/sha512/index.test.ts +17 -2
  98. package/gs/crypto/sha512/index.ts +3 -6
  99. package/gs/embed/index.test.ts +87 -0
  100. package/gs/embed/index.ts +229 -5
  101. package/gs/fmt/fmt.test.ts +61 -3
  102. package/gs/fmt/fmt.ts +115 -22
  103. package/gs/fmt/meta.json +6 -1
  104. package/gs/github.com/aperturerobotics/starpc/srpc/index.test.ts +8 -1
  105. package/gs/github.com/aperturerobotics/starpc/srpc/index.ts +139 -11
  106. package/gs/github.com/aperturerobotics/util/conc/index.test.ts +1 -1
  107. package/gs/github.com/go-git/go-billy/v6/osfs/index.test.ts +110 -0
  108. package/gs/github.com/go-git/go-billy/v6/osfs/index.ts +280 -0
  109. package/gs/github.com/go-git/go-billy/v6/osfs/meta.json +8 -0
  110. package/gs/io/fs/readdir.test.ts +38 -0
  111. package/gs/io/fs/readdir.ts +7 -3
  112. package/gs/io/io.test.ts +135 -0
  113. package/gs/io/io.ts +143 -63
  114. package/gs/io/meta.json +7 -1
  115. package/gs/math/bits/index.ts +52 -28
  116. package/gs/net/http/httptest/index.test.ts +34 -2
  117. package/gs/net/http/httptest/index.ts +23 -8
  118. package/gs/net/http/index.test.ts +46 -0
  119. package/gs/net/http/index.ts +178 -12
  120. package/gs/os/file_unix_js.test.ts +52 -0
  121. package/gs/os/meta.json +4 -0
  122. package/gs/os/readdir.test.ts +56 -0
  123. package/gs/os/types_js.gs.ts +169 -8
  124. package/gs/os/zero_copy_posix.gs.ts +1 -2
  125. package/gs/reflect/deepequal.test.ts +10 -1
  126. package/gs/reflect/type.ts +91 -56
  127. package/gs/reflect/typefor.test.ts +31 -1
  128. package/gs/strings/meta.json +5 -2
  129. package/gs/strings/reader.test.ts +2 -2
  130. package/gs/strings/reader.ts +2 -2
  131. package/gs/sync/meta.json +2 -0
  132. package/gs/sync/sync.test.ts +41 -1
  133. package/gs/sync/sync.ts +41 -16
  134. package/gs/syscall/js/index.test.ts +18 -0
  135. package/gs/syscall/js/index.ts +12 -0
  136. package/gs/testing/testing.test.ts +32 -3
  137. package/gs/testing/testing.ts +13 -10
  138. package/package.json +1 -1
@@ -132,7 +132,7 @@ func TestCompilePackagesEmitsSimplePackage(t *testing.T) {
132
132
  "export async function main(): globalThis.Promise<void>",
133
133
  "let size = 5",
134
134
  "$.print(\"total:\", size)",
135
- "$.println(Greeting, total)",
135
+ "$.println(\"Hello\", total)",
136
136
  "$.panic(\"unreachable\")",
137
137
  "await main()",
138
138
  } {
@@ -215,11 +215,11 @@ func TestCompilePackagesLazilyInitializesCrossFilePackageVars(t *testing.T) {
215
215
  }
216
216
  text := string(content)
217
217
  for _, want := range []string{
218
- "export let two: holder = undefined as unknown as holder",
218
+ "export var two: holder",
219
219
  "export function __goscript_get_two(): holder",
220
- "export let remoteZero: __goscript_a.remote = undefined as unknown as __goscript_a.remote",
220
+ "export var remoteZero: __goscript_a.remote",
221
221
  "export function __goscript_get_remoteZero(): __goscript_a.remote",
222
- "__goscript_a.one",
222
+ "__goscript_a.__goscript_get_one()",
223
223
  } {
224
224
  if !strings.Contains(text, want) {
225
225
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -263,7 +263,7 @@ func TestCompilePackagesLazilyInitializesSameFileLaterPackageVars(t *testing.T)
263
263
  }
264
264
  text := string(content)
265
265
  for _, want := range []string{
266
- "export let table: $.Slice<detail> = undefined as unknown as $.Slice<detail>",
266
+ "export var table: $.Slice<detail>",
267
267
  "export function __goscript_get_table(): $.Slice<detail>",
268
268
  "export let later: detail = $.markAsStructValue(new detail({n: 7}))",
269
269
  } {
@@ -305,7 +305,7 @@ func TestCompilePackagesLazilyInitializesFunctionBodyPackageVarDependencies(t *t
305
305
  }
306
306
  text := string(content)
307
307
  for _, want := range []string{
308
- "export let first: Point | $.VarRef<Point> | null = undefined as unknown as Point | $.VarRef<Point> | null",
308
+ "export var first: Point | $.VarRef<Point> | null",
309
309
  "export function __goscript_get_first(): Point | $.VarRef<Point> | null",
310
310
  "function __goscript_get___goscriptTuple",
311
311
  "export let later: number = 7",
@@ -316,6 +316,67 @@ func TestCompilePackagesLazilyInitializesFunctionBodyPackageVarDependencies(t *t
316
316
  }
317
317
  }
318
318
 
319
+ func TestCompilePackagesLazilyInitializesEffectFreeTypeForFromCrossFileInit(t *testing.T) {
320
+ moduleDir := writePackageGraphFixture(t, map[string]string{
321
+ "go.mod": "module example.test/lazytypefor\n\ngo 1.25.3\n",
322
+ "a.go": strings.Join([]string{
323
+ "package main",
324
+ "import \"reflect\"",
325
+ "var stringType = reflect.TypeFor[string]()",
326
+ "var _ = marker",
327
+ "",
328
+ }, "\n"),
329
+ "b.go": strings.Join([]string{
330
+ "package main",
331
+ "func readStringType() { println(stringType != nil) }",
332
+ "func main() {}",
333
+ "",
334
+ }, "\n"),
335
+ "c.go": strings.Join([]string{
336
+ "package main",
337
+ "var marker = 1",
338
+ "func init() { readStringType() }",
339
+ "",
340
+ }, "\n"),
341
+ })
342
+ outputDir := filepath.Join(t.TempDir(), "output")
343
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
344
+ if err != nil {
345
+ t.Fatal(err.Error())
346
+ }
347
+
348
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
349
+ t.Fatal(err.Error())
350
+ }
351
+ aFile := filepath.Join(outputDir, "@goscript", "example.test", "lazytypefor", "a.gs.ts")
352
+ aContent, err := os.ReadFile(aFile)
353
+ if err != nil {
354
+ t.Fatal(err.Error())
355
+ }
356
+ aText := string(aContent)
357
+ for _, want := range []string{
358
+ "export var stringType: reflect.Type | null",
359
+ "export function __goscript_get_stringType(): reflect.Type | null",
360
+ "stringType = reflect.TypeFor",
361
+ } {
362
+ if !strings.Contains(aText, want) {
363
+ t.Fatalf("missing %q in generated a.go output:\n%s", want, aText)
364
+ }
365
+ }
366
+ bFile := filepath.Join(outputDir, "@goscript", "example.test", "lazytypefor", "b.gs.ts")
367
+ bContent, err := os.ReadFile(bFile)
368
+ if err != nil {
369
+ t.Fatal(err.Error())
370
+ }
371
+ bText := string(bContent)
372
+ if !strings.Contains(bText, "__goscript_a.__goscript_get_stringType() != null") {
373
+ t.Fatalf("missing lazy TypeFor package var getter use:\n%s", bText)
374
+ }
375
+ if strings.Contains(bText, "__goscript_a.stringType != null") {
376
+ t.Fatalf("cross-file init still reads TypeFor package var directly:\n%s", bText)
377
+ }
378
+ }
379
+
319
380
  func TestCompilePackagesInitializesLazyAsyncPackageVarsBeforeInit(t *testing.T) {
320
381
  moduleDir := writePackageGraphFixture(t, map[string]string{
321
382
  "go.mod": "module example.test/lazyasyncvars\n\ngo 1.25.3\n",
@@ -392,8 +453,8 @@ func TestCompilePackagesAssignsLazyPackageVarsDirectly(t *testing.T) {
392
453
  t.Fatal(err.Error())
393
454
  }
394
455
  text := string(content)
395
- if !strings.Contains(text, "table = $.append(__goscript_get_table(), 2)") {
396
- t.Fatalf("missing direct lazy package var assignment:\n%s", text)
456
+ if !strings.Contains(text, "__goscript_set_table($.append(__goscript_get_table(), 2))") {
457
+ t.Fatalf("missing lazy package var assignment through setter:\n%s", text)
397
458
  }
398
459
  if strings.Contains(text, "__goscript_get_table() =") {
399
460
  t.Fatalf("lazy getter used as assignment target:\n%s", text)
@@ -489,6 +550,204 @@ func TestCompilePackagesAliasesForInitShortDeclShadow(t *testing.T) {
489
550
  }
490
551
  }
491
552
 
553
+ func TestCompilePackagesDoesNotAliasPackageShadowWithoutRead(t *testing.T) {
554
+ moduleDir := writePackageGraphFixture(t, map[string]string{
555
+ "go.mod": "module example.test/packageshadow\n\ngo 1.25.3\n",
556
+ "main.go": strings.Join([]string{
557
+ "package main",
558
+ "var value = 1",
559
+ "func main() {",
560
+ " value := 2",
561
+ " var other = 3",
562
+ " println(value, other)",
563
+ "}",
564
+ "",
565
+ }, "\n"),
566
+ })
567
+ outputDir := filepath.Join(t.TempDir(), "output")
568
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
569
+ if err != nil {
570
+ t.Fatal(err.Error())
571
+ }
572
+
573
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
574
+ t.Fatal(err.Error())
575
+ }
576
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "packageshadow", "main.gs.ts"))
577
+ if err != nil {
578
+ t.Fatal(err.Error())
579
+ }
580
+ text := string(content)
581
+ if !strings.Contains(text, "let value = 2") {
582
+ t.Fatalf("local package shadow was not emitted with its source name:\n%s", text)
583
+ }
584
+ if strings.Contains(text, "__goscriptShadow") {
585
+ t.Fatalf("package shadow without initializer read should not allocate a shadow alias:\n%s", text)
586
+ }
587
+ }
588
+
589
+ func TestCompilePackagesAliasesPackageShadowInitializerReads(t *testing.T) {
590
+ moduleDir := writePackageGraphFixture(t, map[string]string{
591
+ "go.mod": "module example.test/packageshadowread\n\ngo 1.25.3\n",
592
+ "main.go": strings.Join([]string{
593
+ "package main",
594
+ "var value = 1",
595
+ "func helper() int { return 4 }",
596
+ "func main() {",
597
+ " println(helper())",
598
+ " helper := 5",
599
+ " println(helper)",
600
+ " first := value",
601
+ " value := value + 1",
602
+ " {",
603
+ " var first = first + value",
604
+ " println(first)",
605
+ " }",
606
+ " println(value)",
607
+ "}",
608
+ "",
609
+ }, "\n"),
610
+ })
611
+ outputDir := filepath.Join(t.TempDir(), "output")
612
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
613
+ if err != nil {
614
+ t.Fatal(err.Error())
615
+ }
616
+
617
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
618
+ t.Fatal(err.Error())
619
+ }
620
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "packageshadowread", "main.gs.ts"))
621
+ if err != nil {
622
+ t.Fatal(err.Error())
623
+ }
624
+ text := string(content)
625
+ if strings.Contains(text, "let value = value + 1") || strings.Contains(text, "let first: number = 0\n\t\tfirst = first + value") {
626
+ t.Fatalf("initializer read was emitted through a TDZ self-reference:\n%s", text)
627
+ }
628
+ if strings.Contains(text, "let helper = 5") {
629
+ t.Fatalf("same-file package function shadow was emitted with a TDZ-prone source name:\n%s", text)
630
+ }
631
+ if strings.Count(text, "__goscriptShadow") < 3 {
632
+ t.Fatalf("missing shadow aliases for initializer reads:\n%s", text)
633
+ }
634
+ }
635
+
636
+ func TestCompilePackagesLowersPackageConstBeforeLocalShadow(t *testing.T) {
637
+ moduleDir := writePackageGraphFixture(t, map[string]string{
638
+ "go.mod": "module example.test/constshadow\n\ngo 1.25.3\n",
639
+ "main.go": strings.Join([]string{
640
+ "package main",
641
+ "const headerLength = 4",
642
+ "func parse(buf []byte) int {",
643
+ " if len(buf) < headerLength {",
644
+ " return headerLength",
645
+ " }",
646
+ " n := len(buf)",
647
+ " headerLength := n",
648
+ " return headerLength",
649
+ "}",
650
+ "",
651
+ }, "\n"),
652
+ })
653
+ outputDir := filepath.Join(t.TempDir(), "output")
654
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
655
+ if err != nil {
656
+ t.Fatal(err.Error())
657
+ }
658
+
659
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
660
+ t.Fatal(err.Error())
661
+ }
662
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "constshadow", "main.gs.ts"))
663
+ if err != nil {
664
+ t.Fatal(err.Error())
665
+ }
666
+ text := string(content)
667
+ if strings.Contains(text, "$.len(buf) < headerLength") || strings.Contains(text, "return headerLength\n\t}") {
668
+ t.Fatalf("package const read was emitted through local TDZ shadow:\n%s", text)
669
+ }
670
+ if !strings.Contains(text, "$.len(buf) < 4") || !strings.Contains(text, "return 4") {
671
+ t.Fatalf("package const read was not lowered to a value before local shadow:\n%s", text)
672
+ }
673
+ if !strings.Contains(text, "let headerLength = n") {
674
+ t.Fatalf("local shadow was not preserved:\n%s", text)
675
+ }
676
+ }
677
+
678
+ func TestCompilePackagesPreservesNamedUint64InterfaceTypeInfo(t *testing.T) {
679
+ moduleDir := writePackageGraphFixture(t, map[string]string{
680
+ "go.mod": "module example.test/nameduint64\n\ngo 1.25.3\n",
681
+ "main.go": strings.Join([]string{
682
+ "package main",
683
+ "type Pol uint64",
684
+ "func (p Pol) String() string { return \"\" }",
685
+ "func box() any {",
686
+ " var p Pol",
687
+ " return &p",
688
+ "}",
689
+ "",
690
+ }, "\n"),
691
+ })
692
+ outputDir := filepath.Join(t.TempDir(), "output")
693
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
694
+ if err != nil {
695
+ t.Fatal(err.Error())
696
+ }
697
+
698
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
699
+ t.Fatal(err.Error())
700
+ }
701
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "nameduint64", "main.gs.ts"))
702
+ if err != nil {
703
+ t.Fatal(err.Error())
704
+ }
705
+ text := string(content)
706
+ want := `$.namedValueInterfaceValue<any>(p, "*main.Pol"`
707
+ if !strings.Contains(text, want) {
708
+ t.Fatalf("missing named interface box %q in generated output:\n%s", want, text)
709
+ }
710
+ want = `elemType: { kind: $.TypeKind.Basic, name: "uint64", typeName: "main.Pol" }`
711
+ if !strings.Contains(text, want) {
712
+ t.Fatalf("named uint64 pointer interface box lost type metadata:\n%s", text)
713
+ }
714
+ }
715
+
716
+ func TestCompilePackagesLowersWideIntegerConstantsForUint64Targets(t *testing.T) {
717
+ moduleDir := writePackageGraphFixture(t, map[string]string{
718
+ "go.mod": "module example.test/wideconst\n\ngo 1.25.3\n",
719
+ "main.go": strings.Join([]string{
720
+ "package main",
721
+ "type Pol uint64",
722
+ "func normalize(f Pol) Pol {",
723
+ " f &= Pol((1 << 54) - 1)",
724
+ " f |= (1 << 53) | 1",
725
+ " return f",
726
+ "}",
727
+ "",
728
+ }, "\n"),
729
+ })
730
+ outputDir := filepath.Join(t.TempDir(), "output")
731
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
732
+ if err != nil {
733
+ t.Fatal(err.Error())
734
+ }
735
+
736
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
737
+ t.Fatal(err.Error())
738
+ }
739
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "wideconst", "main.gs.ts"))
740
+ if err != nil {
741
+ t.Fatal(err.Error())
742
+ }
743
+ text := string(content)
744
+ for _, want := range []string{`$.uint("18014398509481983", 64)`, `$.uint("9007199254740993", 64)`} {
745
+ if !strings.Contains(text, want) {
746
+ t.Fatalf("missing wide integer constant %q in generated output:\n%s", want, text)
747
+ }
748
+ }
749
+ }
750
+
492
751
  func TestCompilePackagesReadsShadowedVarRefStructFieldsOnce(t *testing.T) {
493
752
  moduleDir := writePackageGraphFixture(t, map[string]string{
494
753
  "go.mod": "module example.test/shadowvarreffield\n\ngo 1.25.3\n",
@@ -803,6 +1062,47 @@ func TestCompilePackagesUsesEmbedOverride(t *testing.T) {
803
1062
  }
804
1063
  }
805
1064
 
1065
+ func TestCompilePackagesEmbedsFS(t *testing.T) {
1066
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1067
+ "go.mod": "module example.test/embedfs\n\ngo 1.25.3\n",
1068
+ "assets/config.json": `{"ok":true}`,
1069
+ "assets/nested.txt": "nested",
1070
+ "extra.txt": "extra",
1071
+ "main.go": strings.Join([]string{
1072
+ "package embedfs",
1073
+ "import \"embed\"",
1074
+ "//go:embed assets *.txt",
1075
+ "var StaticFS embed.FS",
1076
+ "",
1077
+ }, "\n"),
1078
+ })
1079
+ outputDir := filepath.Join(t.TempDir(), "output")
1080
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir, AllDependencies: true}, nil, nil)
1081
+ if err != nil {
1082
+ t.Fatal(err.Error())
1083
+ }
1084
+
1085
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1086
+ t.Fatal(err.Error())
1087
+ }
1088
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "embedfs", "main.gs.ts"))
1089
+ if err != nil {
1090
+ t.Fatal(err.Error())
1091
+ }
1092
+ if !strings.Contains(string(content), `export let StaticFS: embed.FS = $.markAsStructValue(new embed.FS`) {
1093
+ t.Fatalf("embedded FS was not emitted as embed.FS:\n%s", string(content))
1094
+ }
1095
+ if !strings.Contains(string(content), `["assets/config.json", new Uint8Array([123, 34, 111, 107, 34, 58, 116, 114, 117, 101, 125])]`) {
1096
+ t.Fatalf("embedded FS file content was not emitted:\n%s", string(content))
1097
+ }
1098
+ if !strings.Contains(string(content), `["assets/nested.txt", new Uint8Array([110, 101, 115, 116, 101, 100])]`) {
1099
+ t.Fatalf("embedded FS directory file content was not emitted:\n%s", string(content))
1100
+ }
1101
+ if !strings.Contains(string(content), `["extra.txt", new Uint8Array([101, 120, 116, 114, 97])]`) {
1102
+ t.Fatalf("embedded FS glob file content was not emitted:\n%s", string(content))
1103
+ }
1104
+ }
1105
+
806
1106
  func TestCompilePackagesEmitsPackageLocalImport(t *testing.T) {
807
1107
  moduleDir := writePackageGraphFixture(t, map[string]string{
808
1108
  "go.mod": "module example.test/imports\n\ngo 1.25.3\n",
@@ -889,6 +1189,94 @@ func TestCompilePackagesEmitsPackageLocalImport(t *testing.T) {
889
1189
  }
890
1190
  }
891
1191
 
1192
+ func TestCompilePackagesCallsCrossPackageUnexportedReceiverDynamically(t *testing.T) {
1193
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1194
+ "go.mod": "module example.test/unexportedreceiver\n\ngo 1.25.3\n",
1195
+ "dep/dep.go": strings.Join([]string{
1196
+ "package dep",
1197
+ "type hidden struct{}",
1198
+ "func NewHidden() *hidden { return &hidden{} }",
1199
+ "func (h *hidden) Value() int { return 7 }",
1200
+ "",
1201
+ }, "\n"),
1202
+ "main.go": strings.Join([]string{
1203
+ "package main",
1204
+ "import \"example.test/unexportedreceiver/dep\"",
1205
+ "func main() {",
1206
+ " h := dep.NewHidden()",
1207
+ " println(h.Value())",
1208
+ "}",
1209
+ "",
1210
+ }, "\n"),
1211
+ })
1212
+ outputDir := filepath.Join(t.TempDir(), "output")
1213
+ comp, err := NewCompiler(&Config{
1214
+ Dir: moduleDir,
1215
+ OutputPath: outputDir,
1216
+ AllDependencies: true,
1217
+ }, nil, nil)
1218
+ if err != nil {
1219
+ t.Fatal(err.Error())
1220
+ }
1221
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1222
+ t.Fatal(err.Error())
1223
+ }
1224
+ mainFile := filepath.Join(outputDir, "@goscript", "example.test", "unexportedreceiver", "main.gs.ts")
1225
+ mainContent, err := os.ReadFile(mainFile)
1226
+ if err != nil {
1227
+ t.Fatal(err.Error())
1228
+ }
1229
+ if !strings.Contains(string(mainContent), "$.pointerValue<any>(h).Value()") {
1230
+ t.Fatalf("cross-package unexported receiver method call was not dynamic:\n%s", string(mainContent))
1231
+ }
1232
+ if strings.Contains(string(mainContent), "dep.hidden.prototype.Value.call") {
1233
+ t.Fatalf("cross-package unexported receiver leaked through imported prototype:\n%s", string(mainContent))
1234
+ }
1235
+ }
1236
+
1237
+ func TestCompilePackagesEmitsTypeOnlyLocalImports(t *testing.T) {
1238
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1239
+ "go.mod": "module example.test/typeonlylocal\n\ngo 1.25.3\n",
1240
+ "a.go": strings.Join([]string{
1241
+ "package main",
1242
+ "type Acceptor interface {",
1243
+ " Accept(Payload)",
1244
+ "}",
1245
+ "",
1246
+ }, "\n"),
1247
+ "payload.go": strings.Join([]string{
1248
+ "package main",
1249
+ "type Payload struct {",
1250
+ " Value string",
1251
+ "}",
1252
+ "",
1253
+ }, "\n"),
1254
+ })
1255
+ outputDir := filepath.Join(t.TempDir(), "output")
1256
+ comp, err := NewCompiler(&Config{
1257
+ Dir: moduleDir,
1258
+ OutputPath: outputDir,
1259
+ }, 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
+ content, err := os.ReadFile(filepath.Join(outputDir, "@goscript", "example.test", "typeonlylocal", "a.gs.ts"))
1268
+ if err != nil {
1269
+ t.Fatal(err.Error())
1270
+ }
1271
+ text := string(content)
1272
+ if !strings.Contains(text, "import type * as __goscript_payload from \"./payload.gs.ts\"") {
1273
+ t.Fatalf("missing type-only same-package import:\n%s", text)
1274
+ }
1275
+ if strings.Contains(text, "import \"./payload.gs.ts\"") {
1276
+ t.Fatalf("type-only same-package import should not force sibling module execution:\n%s", text)
1277
+ }
1278
+ }
1279
+
892
1280
  func TestCompilePackagesPreservesSourceImportAliasesForAssociatedMethods(t *testing.T) {
893
1281
  moduleDir := writePackageGraphFixture(t, map[string]string{
894
1282
  "go.mod": "module example.test/sourcealiases\n\ngo 1.25.3\n",
@@ -1004,8 +1392,7 @@ func TestCompilePackagesEmitsSideEffectImportsForInterfaceRegistry(t *testing.T)
1004
1392
  for _, want := range []string{
1005
1393
  "import * as dep from \"@goscript/example.test/interface-registry/dep/index.js\"",
1006
1394
  "import \"@goscript/example.test/interface-registry/dep/index.js\"",
1007
- "import * as __goscript_local from \"./local.gs.ts\"",
1008
- "import \"./local.gs.ts\"",
1395
+ "import type * as __goscript_local from \"./local.gs.ts\"",
1009
1396
  "case $.typeAssert<Exclude<__goscript_local.Local, null>>(__goscriptTypeSwitchValue, \"main.Local\").ok",
1010
1397
  "$.typeAssertTuple<dep.Remote | null>(v, \"dep.Remote\")",
1011
1398
  } {
@@ -1130,7 +1517,7 @@ func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
1130
1517
  "Counter.prototype.Set.call(NewCounter(), 5)",
1131
1518
  "let [, ok] = $.typeAssertTuple<Counter | $.VarRef<Counter> | null>(iface, { kind: $.TypeKind.Pointer, elemType: \"main.Counter\" })",
1132
1519
  "\"Value\": { type: { kind: $.TypeKind.Basic, name: \"int\" }, tag: \"json:\\\"value\\\"\" }",
1133
- "\"ID\": { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.ObjectID\" }",
1520
+ "\"ID\": { kind: $.TypeKind.Basic, name: \"int32\", typeName: \"main.ObjectID\" }",
1134
1521
  } {
1135
1522
  if !strings.Contains(text, want) {
1136
1523
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -1138,7 +1525,153 @@ func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
1138
1525
  }
1139
1526
  }
1140
1527
 
1141
- func TestCompilePackagesClonesNestedStructFieldsWithCloneMethodCollision(t *testing.T) {
1528
+ func TestCompilePackagesEscapesReservedTypeNames(t *testing.T) {
1529
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1530
+ "go.mod": "module example.test/reservedtypes\n\ngo 1.25.3\n",
1531
+ "main.go": strings.Join([]string{
1532
+ "package main",
1533
+ "type static struct {",
1534
+ " Value int",
1535
+ "}",
1536
+ "func newStatic() *static {",
1537
+ " return &static{Value: 7}",
1538
+ "}",
1539
+ "func (s *static) Read() int {",
1540
+ " return s.Value",
1541
+ "}",
1542
+ "func main() {",
1543
+ " println(newStatic().Read())",
1544
+ "}",
1545
+ "",
1546
+ }, "\n"),
1547
+ })
1548
+ outputDir := filepath.Join(t.TempDir(), "output")
1549
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1550
+ if err != nil {
1551
+ t.Fatal(err.Error())
1552
+ }
1553
+
1554
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1555
+ t.Fatal(err.Error())
1556
+ }
1557
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "reservedtypes", "main.gs.ts")
1558
+ content, err := os.ReadFile(outputFile)
1559
+ if err != nil {
1560
+ t.Fatal(err.Error())
1561
+ }
1562
+ text := string(content)
1563
+ for _, want := range []string{
1564
+ "export class _static",
1565
+ "new _static({Value: 7})",
1566
+ "public Read(): number",
1567
+ "_static.prototype.Read.call(newStatic())",
1568
+ "\"main.static\"",
1569
+ } {
1570
+ if !strings.Contains(text, want) {
1571
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1572
+ }
1573
+ }
1574
+ if strings.Contains(text, "class static") {
1575
+ t.Fatalf("reserved type name was not escaped:\n%s", text)
1576
+ }
1577
+ }
1578
+
1579
+ func TestCompilePackagesAvoidsPointerMethodTypeNameShadow(t *testing.T) {
1580
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1581
+ "go.mod": "module example.test/methodshadow\n\ngo 1.25.3\n",
1582
+ "main.go": strings.Join([]string{
1583
+ "package main",
1584
+ "type item struct {",
1585
+ " Value int",
1586
+ "}",
1587
+ "func (i *item) read() int {",
1588
+ " return i.Value",
1589
+ "}",
1590
+ "func total(items []*item) int {",
1591
+ " sum := 0",
1592
+ " for _, item := range items {",
1593
+ " sum += item.read()",
1594
+ " }",
1595
+ " return sum",
1596
+ "}",
1597
+ "",
1598
+ }, "\n"),
1599
+ })
1600
+ outputDir := filepath.Join(t.TempDir(), "output")
1601
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1602
+ if err != nil {
1603
+ t.Fatal(err.Error())
1604
+ }
1605
+
1606
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1607
+ t.Fatal(err.Error())
1608
+ }
1609
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "methodshadow", "main.gs.ts")
1610
+ content, err := os.ReadFile(outputFile)
1611
+ if err != nil {
1612
+ t.Fatal(err.Error())
1613
+ }
1614
+ text := string(content)
1615
+ if !strings.Contains(text, "$.pointerValue<item>(item).read()") {
1616
+ t.Fatalf("missing direct pointer method call for shadowed type name:\n%s", text)
1617
+ }
1618
+ if strings.Contains(text, "item.prototype.read.call(item") {
1619
+ t.Fatalf("shadowed local variable used as method class:\n%s", text)
1620
+ }
1621
+ }
1622
+
1623
+ func TestCompilePackagesErasesUnimportedTransitiveInterfaceField(t *testing.T) {
1624
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1625
+ "go.mod": "module example.test/transitivefield\n\ngo 1.25.3\n",
1626
+ "hash/hash.go": strings.Join([]string{
1627
+ "package hash",
1628
+ "type Hash interface {",
1629
+ " Write([]byte) (int, error)",
1630
+ "}",
1631
+ "",
1632
+ }, "\n"),
1633
+ "holder/holder.go": strings.Join([]string{
1634
+ "package holder",
1635
+ "import \"example.test/transitivefield/hash\"",
1636
+ "type Hasher struct {",
1637
+ " hash.Hash",
1638
+ "}",
1639
+ "",
1640
+ }, "\n"),
1641
+ "main.go": strings.Join([]string{
1642
+ "package main",
1643
+ "import \"example.test/transitivefield/holder\"",
1644
+ "func write(h holder.Hasher, p []byte) error {",
1645
+ " _, err := h.Hash.Write(p)",
1646
+ " return err",
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", "transitivefield", "main.gs.ts")
1661
+ content, err := os.ReadFile(outputFile)
1662
+ if err != nil {
1663
+ t.Fatal(err.Error())
1664
+ }
1665
+ text := string(content)
1666
+ if !strings.Contains(text, "$.pointerValue<any>(h.Hash).Write(p)") {
1667
+ t.Fatalf("missing erased transitive interface field type:\n%s", text)
1668
+ }
1669
+ if strings.Contains(text, "Exclude<Hash") {
1670
+ t.Fatalf("unimported transitive interface type leaked into output:\n%s", text)
1671
+ }
1672
+ }
1673
+
1674
+ func TestCompilePackagesClonesNestedStructFieldsWithCloneMethodCollision(t *testing.T) {
1142
1675
  moduleDir := writePackageGraphFixture(t, map[string]string{
1143
1676
  "go.mod": "module example.test/nestedclone\n\ngo 1.25.3\n",
1144
1677
  "main.go": strings.Join([]string{
@@ -1341,6 +1874,47 @@ func TestCompilePackagesWrapsAddressedMapRangeValue(t *testing.T) {
1341
1874
  }
1342
1875
  }
1343
1876
 
1877
+ func TestCompilePackagesLowersTupleAssignmentToMapIndexSet(t *testing.T) {
1878
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1879
+ "go.mod": "module example.test/maptupleassign\n\ngo 1.25.3\n",
1880
+ "main.go": strings.Join([]string{
1881
+ "package main",
1882
+ "func resolve(k string) (int, error) { return 7, nil }",
1883
+ "func Apply(keys []string) (map[string]int, error) {",
1884
+ " out := make(map[string]int)",
1885
+ " var err error",
1886
+ " for _, k := range keys {",
1887
+ " out[k], err = resolve(k)",
1888
+ " if err != nil { return nil, err }",
1889
+ " }",
1890
+ " return out, nil",
1891
+ "}",
1892
+ "",
1893
+ }, "\n"),
1894
+ })
1895
+ outputDir := filepath.Join(t.TempDir(), "output")
1896
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1897
+ if err != nil {
1898
+ t.Fatal(err.Error())
1899
+ }
1900
+
1901
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1902
+ t.Fatal(err.Error())
1903
+ }
1904
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "maptupleassign", "main.gs.ts")
1905
+ content, err := os.ReadFile(outputFile)
1906
+ if err != nil {
1907
+ t.Fatal(err.Error())
1908
+ }
1909
+ text := string(content)
1910
+ if !strings.Contains(text, "$.mapSet(out, k, __goscriptTuple") {
1911
+ t.Fatalf("tuple assignment to map index did not write through mapSet:\n%s", text)
1912
+ }
1913
+ if strings.Contains(text, "$.mapGet(out, k, 0)[0] =") {
1914
+ t.Fatalf("tuple assignment still mutates mapGet tuple instead of map:\n%s", text)
1915
+ }
1916
+ }
1917
+
1344
1918
  func TestCompilePackagesLowersPromotedNamedPrimitiveMethod(t *testing.T) {
1345
1919
  moduleDir := writePackageGraphFixture(t, map[string]string{
1346
1920
  "go.mod": "module example.test/promotedprimitive\n\ngo 1.25.3\n",
@@ -1673,6 +2247,51 @@ func TestCompilePackagesLowersStringOrderingThroughRuntime(t *testing.T) {
1673
2247
  }
1674
2248
  }
1675
2249
 
2250
+ func TestCompilePackagesConvertsOverrideNamedStringToString(t *testing.T) {
2251
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2252
+ "go.mod": "module example.test/reflecttag\n\ngo 1.25.3\n",
2253
+ "main.go": strings.Join([]string{
2254
+ "package main",
2255
+ "import (",
2256
+ " \"reflect\"",
2257
+ " \"strings\"",
2258
+ ")",
2259
+ "func tagText(st reflect.Type) string {",
2260
+ " field := st.Field(0)",
2261
+ " tag := field.Tag.Get(\"yaml\")",
2262
+ " if tag == \"\" && strings.Index(string(field.Tag), \":\") < 0 {",
2263
+ " tag = string(field.Tag)",
2264
+ " }",
2265
+ " return tag",
2266
+ "}",
2267
+ "",
2268
+ }, "\n"),
2269
+ })
2270
+ outputDir := filepath.Join(t.TempDir(), "output")
2271
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2272
+ if err != nil {
2273
+ t.Fatal(err.Error())
2274
+ }
2275
+
2276
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2277
+ t.Fatal(err.Error())
2278
+ }
2279
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "reflecttag", "main.gs.ts")
2280
+ content, err := os.ReadFile(outputFile)
2281
+ if err != nil {
2282
+ t.Fatal(err.Error())
2283
+ }
2284
+ text := string(content)
2285
+ for _, want := range []string{
2286
+ `strings.Index(String(field.Tag), ":")`,
2287
+ `tag = String(field.Tag)`,
2288
+ } {
2289
+ if !strings.Contains(text, want) {
2290
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2291
+ }
2292
+ }
2293
+ }
2294
+
1676
2295
  func TestCompilePackagesEmitsInterfacesMethodValuesTypeSwitchesAndFunctionAssertions(t *testing.T) {
1677
2296
  moduleDir := writePackageGraphFixture(t, map[string]string{
1678
2297
  "go.mod": "module example.test/interfaces\n\ngo 1.25.3\n",
@@ -1887,6 +2506,48 @@ func TestCompilePackagesBoxesTypedNilInterfaceValues(t *testing.T) {
1887
2506
  }
1888
2507
  }
1889
2508
 
2509
+ func TestCompilePackagesBoxesAliasPointerInterfacesWithTargetRuntimeType(t *testing.T) {
2510
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2511
+ "go.mod": "module example.test/alias-interface-box\n\ngo 1.25.3\n",
2512
+ "dep/dep.go": strings.Join([]string{
2513
+ "package dep",
2514
+ "type CloudError struct{}",
2515
+ "func (*CloudError) Error() string { return \"cloud\" }",
2516
+ "",
2517
+ }, "\n"),
2518
+ "main.go": strings.Join([]string{
2519
+ "package main",
2520
+ "import \"example.test/alias-interface-box/dep\"",
2521
+ "type cloudError = dep.CloudError",
2522
+ "func Build() error {",
2523
+ " return &cloudError{}",
2524
+ "}",
2525
+ "",
2526
+ }, "\n"),
2527
+ })
2528
+ outputDir := filepath.Join(t.TempDir(), "output")
2529
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2530
+ if err != nil {
2531
+ t.Fatal(err.Error())
2532
+ }
2533
+
2534
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2535
+ t.Fatal(err.Error())
2536
+ }
2537
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "alias-interface-box", "main.gs.ts")
2538
+ content, err := os.ReadFile(outputFile)
2539
+ if err != nil {
2540
+ t.Fatal(err.Error())
2541
+ }
2542
+ text := string(content)
2543
+ if !strings.Contains(text, "$.interfaceValue<$.GoError>(new dep.CloudError(), \"*dep.CloudError\")") {
2544
+ t.Fatalf("alias pointer was not boxed with target runtime type:\n%s", text)
2545
+ }
2546
+ if strings.Contains(text, "*main.cloudError") {
2547
+ t.Fatalf("alias pointer leaked alias runtime type:\n%s", text)
2548
+ }
2549
+ }
2550
+
1890
2551
  func TestCompilePackagesEmitsGenericMethodsAliasesAndDictionaries(t *testing.T) {
1891
2552
  moduleDir := writePackageGraphFixture(t, map[string]string{
1892
2553
  "go.mod": "module example.test/generics\n\ngo 1.25.3\n",
@@ -1951,9 +2612,66 @@ func TestCompilePackagesEmitsGenericMethodsAliasesAndDictionaries(t *testing.T)
1951
2612
  "$.mapSet(seen, 1, {})",
1952
2613
  "$.genericZero(__typeArgs, \"T\", null)",
1953
2614
  "$.callGenericMethod(__typeArgs, \"T\", \"String\", v)",
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)",
2615
+ "ZeroValue({T: { type: { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.MyInt\" }, zero: () => 0, methods: {String: (receiver: any, ...args: any[]) => (MyInt_String as any)(($.isVarRef(receiver) ? receiver.value : receiver), ...args)} }})",
2616
+ "CallString({T: { type: { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.MyInt\" }, zero: () => 0, methods: {String: (receiver: any, ...args: any[]) => (MyInt_String as any)(($.isVarRef(receiver) ? receiver.value : receiver), ...args)} }}, zero)",
2617
+ "Sum({T: { type: { kind: $.TypeKind.Basic, name: \"int\", typeName: \"main.MyInt\" }, zero: () => 0, methods: {String: (receiver: any, ...args: any[]) => (MyInt_String as any)(($.isVarRef(receiver) ? receiver.value : receiver), ...args)} }}, null)",
2618
+ } {
2619
+ if !strings.Contains(text, want) {
2620
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2621
+ }
2622
+ }
2623
+ }
2624
+
2625
+ func TestCompilePackagesInfersGenericTypeArgsFromNamedArgument(t *testing.T) {
2626
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2627
+ "go.mod": "module example.test/genericnamedarg\n\ngo 1.25.3\n",
2628
+ "main.go": strings.Join([]string{
2629
+ "package genericnamedarg",
2630
+ "type Source interface { Load() int }",
2631
+ "type auto struct{}",
2632
+ "func (a *auto) Load() int { return 7 }",
2633
+ "type key[T any] struct { name string }",
2634
+ "type entry struct { factory any }",
2635
+ "var entries = map[string]*entry{}",
2636
+ "func NewKey[T any](name string) key[T] {",
2637
+ " entries[name] = &entry{}",
2638
+ " return key[T]{name: name}",
2639
+ "}",
2640
+ "func Register[T any](key key[T], factory func() T) {",
2641
+ " entries[key.name].factory = factory",
2642
+ "}",
2643
+ "func Get[T any](key key[T]) T {",
2644
+ " f := entries[key.name].factory",
2645
+ " return f.(func() T)()",
2646
+ "}",
2647
+ "var loader = NewKey[Source](\"source\")",
2648
+ "func Use() int {",
2649
+ " Register(loader, func() Source { return &auto{} })",
2650
+ " src := Get(loader)",
2651
+ " return src.Load()",
2652
+ "}",
2653
+ "",
2654
+ }, "\n"),
2655
+ })
2656
+ outputDir := filepath.Join(t.TempDir(), "output")
2657
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2658
+ if err != nil {
2659
+ t.Fatal(err.Error())
2660
+ }
2661
+
2662
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2663
+ t.Fatal(err.Error())
2664
+ }
2665
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "genericnamedarg", "main.gs.ts")
2666
+ content, err := os.ReadFile(outputFile)
2667
+ if err != nil {
2668
+ t.Fatal(err.Error())
2669
+ }
2670
+ text := string(content)
2671
+ for _, want := range []string{
2672
+ "await Get({T: { type: \"genericnamedarg.Source\", zero: () => null, methods: {Load: (receiver: any, ...args: any[]) => receiver.Load(...args)} }}, $.markAsStructValue($.cloneStructValue(loader)))",
2673
+ "return await $.mustTypeAssert<(() => any | globalThis.Promise<any>) | null>(f, ({ kind: $.TypeKind.Function, params: [], results: [{ kind: $.TypeKind.Interface, methods: [] }] } as $.FunctionTypeInfo))!()",
2674
+ "return $.pointerValue<Exclude<Source, null>>(src).Load()",
1957
2675
  } {
1958
2676
  if !strings.Contains(text, want) {
1959
2677
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -1974,8 +2692,10 @@ func TestCompilePackagesAttachesFunctionLiteralTypeInfo(t *testing.T) {
1974
2692
  " fn := func(value int) string {",
1975
2693
  " return \"\"",
1976
2694
  " }",
2695
+ " var zero Callback",
1977
2696
  " var cb Callback = nil",
1978
2697
  " _ = fn",
2698
+ " _ = zero",
1979
2699
  " _ = cb",
1980
2700
  " _ = call(fn)",
1981
2701
  "}",
@@ -2000,6 +2720,8 @@ func TestCompilePackagesAttachesFunctionLiteralTypeInfo(t *testing.T) {
2000
2720
  for _, want := range []string{
2001
2721
  "export type Callback = ((value: number) => string | globalThis.Promise<string>) | null",
2002
2722
  "export async function call(cb: ((value: number) => string | globalThis.Promise<string>) | null): globalThis.Promise<string> {\n\treturn await cb!(1)",
2723
+ "let zero: Callback | null = null as unknown as Callback | null",
2724
+ "let cb: Callback | null = (null as Callback | null)",
2003
2725
  "$.functionValue((value: number): string => {",
2004
2726
  "kind: $.TypeKind.Function",
2005
2727
  "params: [{ kind: $.TypeKind.Basic, name: \"int\" }]",
@@ -2162,31 +2884,21 @@ func TestCompilePackagesPacksVariadicCallsInGeneratedSubpackage(t *testing.T) {
2162
2884
  }
2163
2885
  }
2164
2886
 
2165
- func TestCompilePackagesImportsSelectedExternalFieldTypes(t *testing.T) {
2887
+ func TestCompilePackagesBoxesNumericVariadicInterfaceArgs(t *testing.T) {
2166
2888
  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"),
2889
+ "go.mod": "module example.test/numeric-interface\n\ngo 1.25.3\n",
2179
2890
  "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",
2891
+ "package numericinterface",
2892
+ "func collect(values ...any) {}",
2893
+ "func main() {",
2894
+ " var version int32 = 2",
2895
+ " collect(version, uint32(3))",
2184
2896
  "}",
2185
2897
  "",
2186
2898
  }, "\n"),
2187
2899
  })
2188
2900
  outputDir := filepath.Join(t.TempDir(), "output")
2189
- comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir, AllDependencies: true}, nil, nil)
2901
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2190
2902
  if err != nil {
2191
2903
  t.Fatal(err.Error())
2192
2904
  }
@@ -2194,15 +2906,15 @@ func TestCompilePackagesImportsSelectedExternalFieldTypes(t *testing.T) {
2194
2906
  if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2195
2907
  t.Fatal(err.Error())
2196
2908
  }
2197
- outputFile := filepath.Join(outputDir, "@goscript", "example.test", "selected-field-import", "main.gs.ts")
2909
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "numeric-interface", "main.gs.ts")
2198
2910
  content, err := os.ReadFile(outputFile)
2199
2911
  if err != nil {
2200
2912
  t.Fatal(err.Error())
2201
2913
  }
2202
2914
  text := string(content)
2203
2915
  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",
2916
+ "$.namedValueInterfaceValue<any>(version, \"int32\", {}, { kind: $.TypeKind.Basic, name: \"int32\" })",
2917
+ "$.namedValueInterfaceValue<any>($.uint(3, 32), \"uint32\", {}, { kind: $.TypeKind.Basic, name: \"uint32\" })",
2206
2918
  } {
2207
2919
  if !strings.Contains(text, want) {
2208
2920
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -2210,22 +2922,114 @@ func TestCompilePackagesImportsSelectedExternalFieldTypes(t *testing.T) {
2210
2922
  }
2211
2923
  }
2212
2924
 
2213
- func TestCompilePackagesErasesUnavailableOverrideFieldTypes(t *testing.T) {
2925
+ func TestCompilePackagesAwaitsFmtWriterOverrides(t *testing.T) {
2214
2926
  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 }",
2927
+ "go.mod": "module example.test/fmt-writer\n\ngo 1.25.3\n",
2928
+ "main.go": strings.Join([]string{
2929
+ "package fmtwriter",
2930
+ "import \"fmt\"",
2931
+ "type writer struct { buf []byte }",
2932
+ "func (w *writer) Write(p []byte) (int, error) {",
2933
+ " w.buf = append(w.buf, p...)",
2934
+ " return len(p), nil",
2935
+ "}",
2936
+ "func Use(w *writer) error {",
2937
+ " _, err := fmt.Fprintf(w, \"%s\", \"ok\")",
2938
+ " return err",
2939
+ "}",
2225
2940
  "",
2226
2941
  }, "\n"),
2227
- "main.go": strings.Join([]string{
2228
- "package main",
2942
+ })
2943
+ outputDir := filepath.Join(t.TempDir(), "output")
2944
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
2945
+ if err != nil {
2946
+ t.Fatal(err.Error())
2947
+ }
2948
+
2949
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2950
+ t.Fatal(err.Error())
2951
+ }
2952
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "fmt-writer", "main.gs.ts")
2953
+ content, err := os.ReadFile(outputFile)
2954
+ if err != nil {
2955
+ t.Fatal(err.Error())
2956
+ }
2957
+ text := string(content)
2958
+ for _, want := range []string{
2959
+ "export async function Use",
2960
+ "await fmt.Fprintf(",
2961
+ "return err",
2962
+ } {
2963
+ if !strings.Contains(text, want) {
2964
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
2965
+ }
2966
+ }
2967
+ }
2968
+
2969
+ func TestCompilePackagesImportsSelectedExternalFieldTypes(t *testing.T) {
2970
+ moduleDir := writePackageGraphFixture(t, map[string]string{
2971
+ "go.mod": "module example.test/selected-field-import\n\ngo 1.25.3\n",
2972
+ "dep/dep.go": strings.Join([]string{
2973
+ "package dep",
2974
+ "type URL struct { Path string }",
2975
+ "",
2976
+ }, "\n"),
2977
+ "api/api.go": strings.Join([]string{
2978
+ "package api",
2979
+ "import \"example.test/selected-field-import/dep\"",
2980
+ "type Request struct { URL *dep.URL }",
2981
+ "",
2982
+ }, "\n"),
2983
+ "main.go": strings.Join([]string{
2984
+ "package main",
2985
+ "import \"example.test/selected-field-import/api\"",
2986
+ "func requestPath(r *api.Request) string {",
2987
+ " return r.URL.Path",
2988
+ "}",
2989
+ "",
2990
+ }, "\n"),
2991
+ })
2992
+ outputDir := filepath.Join(t.TempDir(), "output")
2993
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir, AllDependencies: true}, nil, nil)
2994
+ if err != nil {
2995
+ t.Fatal(err.Error())
2996
+ }
2997
+
2998
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
2999
+ t.Fatal(err.Error())
3000
+ }
3001
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "selected-field-import", "main.gs.ts")
3002
+ content, err := os.ReadFile(outputFile)
3003
+ if err != nil {
3004
+ t.Fatal(err.Error())
3005
+ }
3006
+ text := string(content)
3007
+ for _, want := range []string{
3008
+ "import * as dep from \"@goscript/example.test/selected-field-import/dep/index.js\"",
3009
+ "$.pointerValue<dep.URL>($.pointerValue<api.Request>(r).URL).Path",
3010
+ } {
3011
+ if !strings.Contains(text, want) {
3012
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
3013
+ }
3014
+ }
3015
+ }
3016
+
3017
+ func TestCompilePackagesErasesUnavailableOverrideFieldTypes(t *testing.T) {
3018
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3019
+ "go.mod": "module example.test/override-field-type\n\ngo 1.25.3\n",
3020
+ "dep/dep.go": strings.Join([]string{
3021
+ "package dep",
3022
+ "type URL struct { Path string }",
3023
+ "",
3024
+ }, "\n"),
3025
+ "api/api.go": strings.Join([]string{
3026
+ "package api",
3027
+ "import \"example.test/override-field-type/dep\"",
3028
+ "type Request struct { URL *dep.URL }",
3029
+ "",
3030
+ }, "\n"),
3031
+ "main.go": strings.Join([]string{
3032
+ "package main",
2229
3033
  "import \"example.test/override-field-type/api\"",
2230
3034
  "func requestPath(r *api.Request) string {",
2231
3035
  " return r.URL.Path",
@@ -2555,6 +3359,468 @@ func TestCompilePackagesEmitsAsyncChannelsSelectAndDefer(t *testing.T) {
2555
3359
  }
2556
3360
  }
2557
3361
 
3362
+ func TestCompilePackagesPropagatesAsyncGenericInterfaceMethods(t *testing.T) {
3363
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3364
+ "go.mod": "module example.test/genericasynciface\n\ngo 1.25.3\n",
3365
+ "main.go": strings.Join([]string{
3366
+ "package genericasynciface",
3367
+ "import \"context\"",
3368
+ "type Watchable[T comparable] interface {",
3369
+ " Get() T",
3370
+ " Wait(ctx context.Context, old T) T",
3371
+ "}",
3372
+ "type Box[T comparable] struct { ch chan T; val T }",
3373
+ "func (b *Box[T]) Get() T { return b.val }",
3374
+ "func (b *Box[T]) Wait(ctx context.Context, old T) T {",
3375
+ " select {",
3376
+ " case v := <-b.ch:",
3377
+ " return v",
3378
+ " case <-ctx.Done():",
3379
+ " return old",
3380
+ " }",
3381
+ "}",
3382
+ "func Use(ctx context.Context, w Watchable[int], old int) int {",
3383
+ " return w.Wait(ctx, old)",
3384
+ "}",
3385
+ "",
3386
+ }, "\n"),
3387
+ })
3388
+ outputDir := filepath.Join(t.TempDir(), "output")
3389
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3390
+ if err != nil {
3391
+ t.Fatal(err.Error())
3392
+ }
3393
+
3394
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3395
+ t.Fatal(err.Error())
3396
+ }
3397
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "genericasynciface", "main.gs.ts")
3398
+ content, err := os.ReadFile(outputFile)
3399
+ if err != nil {
3400
+ t.Fatal(err.Error())
3401
+ }
3402
+ text := string(content)
3403
+ for _, want := range []string{
3404
+ "Wait(ctx: context.Context | null, old: any): any | globalThis.Promise<any>",
3405
+ "public async Wait(ctx: context.Context | null, old: any): globalThis.Promise<any>",
3406
+ "return (await $.pointerValue<Exclude<Watchable, null>>(w).Wait(ctx, old) as number)",
3407
+ } {
3408
+ if !strings.Contains(text, want) {
3409
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
3410
+ }
3411
+ }
3412
+ }
3413
+
3414
+ func TestCompilePackagesPropagatesAsyncAnonymousInterfaceMethods(t *testing.T) {
3415
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3416
+ "go.mod": "module example.test/anonymousasynciface\n\ngo 1.25.3\n",
3417
+ "main.go": strings.Join([]string{
3418
+ "package anonymousasynciface",
3419
+ "import \"context\"",
3420
+ "type Snapshot int",
3421
+ "type Watcher struct { ch chan Snapshot }",
3422
+ "func (w *Watcher) WaitValueChange(ctx context.Context, old Snapshot, errCh <-chan error) (Snapshot, error) {",
3423
+ " select {",
3424
+ " case v := <-w.ch:",
3425
+ " return v, nil",
3426
+ " case err := <-errCh:",
3427
+ " return old, err",
3428
+ " case <-ctx.Done():",
3429
+ " return old, ctx.Err()",
3430
+ " }",
3431
+ "}",
3432
+ "func Use(ctx context.Context, w interface {",
3433
+ " WaitValueChange(context.Context, Snapshot, <-chan error) (Snapshot, error)",
3434
+ "}, old Snapshot) (Snapshot, error) {",
3435
+ " return w.WaitValueChange(ctx, old, nil)",
3436
+ "}",
3437
+ "",
3438
+ }, "\n"),
3439
+ })
3440
+ outputDir := filepath.Join(t.TempDir(), "output")
3441
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3442
+ if err != nil {
3443
+ t.Fatal(err.Error())
3444
+ }
3445
+
3446
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3447
+ t.Fatal(err.Error())
3448
+ }
3449
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "anonymousasynciface", "main.gs.ts")
3450
+ content, err := os.ReadFile(outputFile)
3451
+ if err != nil {
3452
+ t.Fatal(err.Error())
3453
+ }
3454
+ text := string(content)
3455
+ for _, want := range []string{
3456
+ "export async function Use(ctx: context.Context | null, w: any, old: Snapshot): globalThis.Promise<[Snapshot, $.GoError]>",
3457
+ "return await $.pointerValue<any>(w).WaitValueChange(ctx, old, null)",
3458
+ } {
3459
+ if !strings.Contains(text, want) {
3460
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
3461
+ }
3462
+ }
3463
+ }
3464
+
3465
+ func TestCompilePackagesPropagatesAsyncThroughInstantiatedNamedInterface(t *testing.T) {
3466
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3467
+ "go.mod": "module example.test/instantiatedasynciface\n\ngo 1.25.3\n",
3468
+ "main.go": strings.Join([]string{
3469
+ "package instantiatedasynciface",
3470
+ "import \"context\"",
3471
+ "type Snapshot int",
3472
+ "type Watchable[T any] interface {",
3473
+ " WaitValueChange(context.Context, T, <-chan error) (T, error)",
3474
+ "}",
3475
+ "type Container[T any] struct { ch chan T }",
3476
+ "func (c *Container[T]) WaitValueChange(ctx context.Context, old T, errCh <-chan error) (T, error) {",
3477
+ " select {",
3478
+ " case v := <-c.ch:",
3479
+ " return v, nil",
3480
+ " case err := <-errCh:",
3481
+ " return old, err",
3482
+ " case <-ctx.Done():",
3483
+ " return old, ctx.Err()",
3484
+ " }",
3485
+ "}",
3486
+ "func Bind(w Watchable[Snapshot]) {}",
3487
+ "func Use(ctx context.Context, w interface {",
3488
+ " WaitValueChange(context.Context, Snapshot, <-chan error) (Snapshot, error)",
3489
+ "}, old Snapshot) (Snapshot, error) {",
3490
+ " return w.WaitValueChange(ctx, old, nil)",
3491
+ "}",
3492
+ "",
3493
+ }, "\n"),
3494
+ })
3495
+ outputDir := filepath.Join(t.TempDir(), "output")
3496
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3497
+ if err != nil {
3498
+ t.Fatal(err.Error())
3499
+ }
3500
+
3501
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3502
+ t.Fatal(err.Error())
3503
+ }
3504
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "instantiatedasynciface", "main.gs.ts")
3505
+ content, err := os.ReadFile(outputFile)
3506
+ if err != nil {
3507
+ t.Fatal(err.Error())
3508
+ }
3509
+ text := string(content)
3510
+ for _, want := range []string{
3511
+ "export async function Use(ctx: context.Context | null, w: any, old: Snapshot): globalThis.Promise<[Snapshot, $.GoError]>",
3512
+ "return await $.pointerValue<any>(w).WaitValueChange(ctx, old, null)",
3513
+ } {
3514
+ if !strings.Contains(text, want) {
3515
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
3516
+ }
3517
+ }
3518
+ }
3519
+
3520
+ func TestCompilePackagesDoesNotAwaitUnmarkedAnonymousInterfaceMethod(t *testing.T) {
3521
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3522
+ "go.mod": "module example.test/anonymousifaceawait\n\ngo 1.25.3\n",
3523
+ "main.go": strings.Join([]string{
3524
+ "package anonymousifaceawait",
3525
+ "import \"context\"",
3526
+ "type Snapshot int",
3527
+ "func Use(ctx context.Context, w interface {",
3528
+ " WaitValueChange(context.Context, Snapshot, <-chan error) (Snapshot, error)",
3529
+ "}, ch <-chan struct{}, old Snapshot) (Snapshot, error) {",
3530
+ " select {",
3531
+ " case <-ch:",
3532
+ " default:",
3533
+ " }",
3534
+ " return w.WaitValueChange(ctx, old, nil)",
3535
+ "}",
3536
+ "",
3537
+ }, "\n"),
3538
+ })
3539
+ outputDir := filepath.Join(t.TempDir(), "output")
3540
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3541
+ if err != nil {
3542
+ t.Fatal(err.Error())
3543
+ }
3544
+
3545
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3546
+ t.Fatal(err.Error())
3547
+ }
3548
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "anonymousifaceawait", "main.gs.ts")
3549
+ content, err := os.ReadFile(outputFile)
3550
+ if err != nil {
3551
+ t.Fatal(err.Error())
3552
+ }
3553
+ text := string(content)
3554
+ if strings.Contains(text, "return await $.pointerValue<any>(w).WaitValueChange(ctx, old, null)") {
3555
+ t.Fatalf("anonymous interface method call without an async implementation was awaited:\n%s", text)
3556
+ }
3557
+ }
3558
+
3559
+ func TestCompilePackagesDoesNotInheritAsyncIntoSyncFunctionLiteral(t *testing.T) {
3560
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3561
+ "go.mod": "module example.test/syncfunclit\n\ngo 1.25.3\n",
3562
+ "main.go": strings.Join([]string{
3563
+ "package syncfunclit",
3564
+ "type Directive interface{ GetDirective() any }",
3565
+ "type Bridge struct{ keep func(Directive) (bool, error) }",
3566
+ "func NewBridge(keep func(Directive) (bool, error)) *Bridge { return &Bridge{keep: keep} }",
3567
+ "func Execute(ch <-chan struct{}) error {",
3568
+ " select {",
3569
+ " case <-ch:",
3570
+ " default:",
3571
+ " }",
3572
+ " _ = NewBridge(func(di Directive) (bool, error) {",
3573
+ " switch di.GetDirective().(type) {",
3574
+ " default:",
3575
+ " return true, nil",
3576
+ " }",
3577
+ " })",
3578
+ " return nil",
3579
+ "}",
3580
+ "",
3581
+ }, "\n"),
3582
+ })
3583
+ outputDir := filepath.Join(t.TempDir(), "output")
3584
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3585
+ if err != nil {
3586
+ t.Fatal(err.Error())
3587
+ }
3588
+
3589
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3590
+ t.Fatal(err.Error())
3591
+ }
3592
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "syncfunclit", "main.gs.ts")
3593
+ content, err := os.ReadFile(outputFile)
3594
+ if err != nil {
3595
+ t.Fatal(err.Error())
3596
+ }
3597
+ text := string(content)
3598
+ if strings.Contains(text, "await $.pointerValue<Exclude<Directive, null>>(di).GetDirective()") {
3599
+ t.Fatalf("sync function literal inherited async await:\n%s", text)
3600
+ }
3601
+ }
3602
+
3603
+ func TestCompilePackagesMarksRangeFuncAsyncWhenBodyAwaits(t *testing.T) {
3604
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3605
+ "go.mod": "module example.test/rangefuncawaitbody\n\ngo 1.25.3\n",
3606
+ "main.go": strings.Join([]string{
3607
+ "package rangefuncawaitbody",
3608
+ "type Item struct{}",
3609
+ "func (i *Item) Release(ch <-chan struct{}) {",
3610
+ " select {",
3611
+ " case <-ch:",
3612
+ " default:",
3613
+ " }",
3614
+ "}",
3615
+ "func Back(items []Item) func(func(int, Item) bool) {",
3616
+ " return func(yield func(int, Item) bool) {",
3617
+ " for i := len(items)-1; i >= 0; i-- {",
3618
+ " if !yield(i, items[i]) { return }",
3619
+ " }",
3620
+ " }",
3621
+ "}",
3622
+ "func Use(ch <-chan struct{}, items []Item) {",
3623
+ " defer func() {",
3624
+ " for _, v := range Back(items) {",
3625
+ " v.Release(ch)",
3626
+ " }",
3627
+ " }()",
3628
+ "}",
3629
+ "",
3630
+ }, "\n"),
3631
+ })
3632
+ outputDir := filepath.Join(t.TempDir(), "output")
3633
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3634
+ if err != nil {
3635
+ t.Fatal(err.Error())
3636
+ }
3637
+
3638
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3639
+ t.Fatal(err.Error())
3640
+ }
3641
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "rangefuncawaitbody", "main.gs.ts")
3642
+ content, err := os.ReadFile(outputFile)
3643
+ if err != nil {
3644
+ t.Fatal(err.Error())
3645
+ }
3646
+ text := string(content)
3647
+ for _, want := range []string{
3648
+ ";await (async () => {",
3649
+ "await v.value.Release(ch)",
3650
+ } {
3651
+ if !strings.Contains(text, want) {
3652
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
3653
+ }
3654
+ }
3655
+ }
3656
+
3657
+ func TestCompilePackagesPropagatesImportedAsyncGenericInterfaceMethods(t *testing.T) {
3658
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3659
+ "go.mod": "module example.test/importedgenericasynciface\n\ngo 1.25.3\n",
3660
+ "dep/dep.go": strings.Join([]string{
3661
+ "package dep",
3662
+ "import \"context\"",
3663
+ "type Watchable[T comparable] interface {",
3664
+ " Get() T",
3665
+ " Wait(ctx context.Context, old T) T",
3666
+ "}",
3667
+ "type Box[T comparable] struct { ch chan T; val T }",
3668
+ "func (b *Box[T]) Get() T { return b.val }",
3669
+ "func (b *Box[T]) Wait(ctx context.Context, old T) T {",
3670
+ " select {",
3671
+ " case v := <-b.ch:",
3672
+ " return v",
3673
+ " case <-ctx.Done():",
3674
+ " return old",
3675
+ " }",
3676
+ "}",
3677
+ "",
3678
+ }, "\n"),
3679
+ "main.go": strings.Join([]string{
3680
+ "package importedgenericasynciface",
3681
+ "import (",
3682
+ " \"context\"",
3683
+ " \"example.test/importedgenericasynciface/dep\"",
3684
+ ")",
3685
+ "func Use(ctx context.Context, w dep.Watchable[int], old int) int {",
3686
+ " return w.Wait(ctx, old)",
3687
+ "}",
3688
+ "",
3689
+ }, "\n"),
3690
+ })
3691
+ outputDir := filepath.Join(t.TempDir(), "output")
3692
+ service := NewCompileService()
3693
+ _, err := service.Compile(context.Background(), &CompileRequest{
3694
+ Patterns: []string{".", "./dep"},
3695
+ Dir: moduleDir,
3696
+ OutputPath: outputDir,
3697
+ DependencyMode: DependencyModeAll,
3698
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
3699
+ })
3700
+ if err != nil {
3701
+ t.Fatal(err.Error())
3702
+ }
3703
+ depOutputFile := filepath.Join(outputDir, "@goscript", "example.test", "importedgenericasynciface", "dep", "dep.gs.ts")
3704
+ depContent, err := os.ReadFile(depOutputFile)
3705
+ if err != nil {
3706
+ t.Fatal(err.Error())
3707
+ }
3708
+ depText := string(depContent)
3709
+ for _, want := range []string{
3710
+ "Wait(ctx: context.Context | null, old: any): any | globalThis.Promise<any>",
3711
+ "public async Wait(ctx: context.Context | null, old: any): globalThis.Promise<any>",
3712
+ } {
3713
+ if !strings.Contains(depText, want) {
3714
+ t.Fatalf("missing %q in generated dep output:\n%s", want, depText)
3715
+ }
3716
+ }
3717
+
3718
+ mainOutputFile := filepath.Join(outputDir, "@goscript", "example.test", "importedgenericasynciface", "main.gs.ts")
3719
+ mainContent, err := os.ReadFile(mainOutputFile)
3720
+ if err != nil {
3721
+ t.Fatal(err.Error())
3722
+ }
3723
+ mainText := string(mainContent)
3724
+ if want := "return (await $.pointerValue<Exclude<dep.Watchable, null>>(w).Wait(ctx, old) as number)"; !strings.Contains(mainText, want) {
3725
+ t.Fatalf("missing %q in generated main output:\n%s", want, mainText)
3726
+ }
3727
+ }
3728
+
3729
+ func TestCompilePackagesPropagatesAsyncInterfaceMethodsFromTestImports(t *testing.T) {
3730
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3731
+ "go.mod": "module example.test/testimportasynciface\n\ngo 1.25.3\n",
3732
+ "iface/provider.go": strings.Join([]string{
3733
+ "package iface",
3734
+ "import \"context\"",
3735
+ "type Provider interface {",
3736
+ " Create(context.Context) (string, error)",
3737
+ "}",
3738
+ "",
3739
+ }, "\n"),
3740
+ "impl/provider.go": strings.Join([]string{
3741
+ "package impl",
3742
+ "import (",
3743
+ " \"context\"",
3744
+ " \"example.test/testimportasynciface/iface\"",
3745
+ ")",
3746
+ "type Provider struct { ch chan string }",
3747
+ "func NewProvider() iface.Provider {",
3748
+ " return &Provider{ch: make(chan string, 1)}",
3749
+ "}",
3750
+ "func (p *Provider) Create(ctx context.Context) (string, error) {",
3751
+ " select {",
3752
+ " case p.ch <- \"ok\":",
3753
+ " case <-ctx.Done():",
3754
+ " return \"\", ctx.Err()",
3755
+ " }",
3756
+ " return <-p.ch, nil",
3757
+ "}",
3758
+ "",
3759
+ }, "\n"),
3760
+ "use.go": strings.Join([]string{
3761
+ "package testimportasynciface",
3762
+ "import (",
3763
+ " \"context\"",
3764
+ " \"example.test/testimportasynciface/iface\"",
3765
+ ")",
3766
+ "func Use(ctx context.Context, p iface.Provider) (string, error) {",
3767
+ " return p.Create(ctx)",
3768
+ "}",
3769
+ "",
3770
+ }, "\n"),
3771
+ "use_test.go": strings.Join([]string{
3772
+ "package testimportasynciface",
3773
+ "import (",
3774
+ " \"context\"",
3775
+ " \"testing\"",
3776
+ " \"example.test/testimportasynciface/impl\"",
3777
+ ")",
3778
+ "func TestUse(t *testing.T) {",
3779
+ " p := impl.NewProvider()",
3780
+ " got, err := p.Create(context.Background())",
3781
+ " if err != nil || got != \"ok\" {",
3782
+ " t.Fatal(got, err)",
3783
+ " }",
3784
+ "}",
3785
+ "",
3786
+ }, "\n"),
3787
+ })
3788
+ outputDir := filepath.Join(t.TempDir(), "output")
3789
+ service := NewCompileService()
3790
+ _, err := service.Compile(context.Background(), &CompileRequest{
3791
+ Patterns: []string{"."},
3792
+ Dir: moduleDir,
3793
+ OutputPath: outputDir,
3794
+ DependencyMode: DependencyModeAll,
3795
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
3796
+ Tests: true,
3797
+ AllDependencies: true,
3798
+ })
3799
+ if err != nil {
3800
+ t.Fatal(err.Error())
3801
+ }
3802
+
3803
+ ifaceOutputFile := filepath.Join(outputDir, "@goscript", "example.test", "testimportasynciface", "iface", "provider.gs.ts")
3804
+ ifaceContent, err := os.ReadFile(ifaceOutputFile)
3805
+ if err != nil {
3806
+ t.Fatal(err.Error())
3807
+ }
3808
+ ifaceText := string(ifaceContent)
3809
+ if want := "Create(_p0: context.Context | null): [string, $.GoError] | globalThis.Promise<[string, $.GoError]>"; !strings.Contains(ifaceText, want) {
3810
+ t.Fatalf("test-import implementation did not color interface method async:\n%s", ifaceText)
3811
+ }
3812
+
3813
+ testOutputFile := filepath.Join(outputDir, "@goscript", "example.test", "testimportasynciface", "use_test.gs.ts")
3814
+ testContent, err := os.ReadFile(testOutputFile)
3815
+ if err != nil {
3816
+ t.Fatal(err.Error())
3817
+ }
3818
+ testText := string(testContent)
3819
+ if want := "let [got, err] = await $.pointerValue<Exclude<iface.Provider, null>>(p).Create(context.Background())"; !strings.Contains(testText, want) {
3820
+ t.Fatalf("test package call was not awaited:\n%s", testText)
3821
+ }
3822
+ }
3823
+
2558
3824
  func TestCompilePackagesMarksSelectReturningIfElseCasesUnreachable(t *testing.T) {
2559
3825
  moduleDir := writePackageGraphFixture(t, map[string]string{
2560
3826
  "go.mod": "module example.test/select-if-else\n\ngo 1.25.3\n",
@@ -2602,6 +3868,91 @@ func TestCompilePackagesMarksSelectReturningIfElseCasesUnreachable(t *testing.T)
2602
3868
  }
2603
3869
  }
2604
3870
 
3871
+ func TestCompilePackagesAddsUnreachableReturnFallback(t *testing.T) {
3872
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3873
+ "go.mod": "module example.test/select-named\n\ngo 1.25.3\n",
3874
+ "main.go": strings.Join([]string{
3875
+ "package main",
3876
+ "import \"context\"",
3877
+ "func wait(ctx context.Context, ch <-chan error) (rerr error) {",
3878
+ " select {",
3879
+ " case <-ctx.Done():",
3880
+ " return context.Canceled",
3881
+ " case err := <-ch:",
3882
+ " return err",
3883
+ " }",
3884
+ "}",
3885
+ "",
3886
+ }, "\n"),
3887
+ })
3888
+ outputDir := filepath.Join(t.TempDir(), "output")
3889
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3890
+ if err != nil {
3891
+ t.Fatal(err.Error())
3892
+ }
3893
+
3894
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3895
+ t.Fatal(err.Error())
3896
+ }
3897
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "select-named", "main.gs.ts")
3898
+ content, err := os.ReadFile(outputFile)
3899
+ if err != nil {
3900
+ t.Fatal(err.Error())
3901
+ }
3902
+ text := string(content)
3903
+ for _, want := range []string{
3904
+ "export async function wait(ctx: context.Context | null, ch: $.Channel<$.GoError> | null): globalThis.Promise<$.GoError>",
3905
+ "let rerr: $.GoError = null as $.GoError",
3906
+ "throw new globalThis.Error(\"goscript: unreachable return\")",
3907
+ } {
3908
+ if !strings.Contains(text, want) {
3909
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
3910
+ }
3911
+ }
3912
+ }
3913
+
3914
+ func TestCompilePackagesAnnotatesShortDeclInterfaceValues(t *testing.T) {
3915
+ moduleDir := writePackageGraphFixture(t, map[string]string{
3916
+ "go.mod": "module example.test/interface-short-decl\n\ngo 1.25.3\n",
3917
+ "main.go": strings.Join([]string{
3918
+ "package main",
3919
+ "type Reader interface { Read() int }",
3920
+ "type impl struct{ value int }",
3921
+ "func (i *impl) Read() int { return i.value }",
3922
+ "func replacement() Reader { return &impl{value: 2} }",
3923
+ "func use(r Reader, swap bool) int {",
3924
+ " if r == nil {",
3925
+ " return 0",
3926
+ " }",
3927
+ " current := r",
3928
+ " if swap {",
3929
+ " current = replacement()",
3930
+ " }",
3931
+ " return current.Read()",
3932
+ "}",
3933
+ "",
3934
+ }, "\n"),
3935
+ })
3936
+ outputDir := filepath.Join(t.TempDir(), "output")
3937
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
3938
+ if err != nil {
3939
+ t.Fatal(err.Error())
3940
+ }
3941
+
3942
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
3943
+ t.Fatal(err.Error())
3944
+ }
3945
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "interface-short-decl", "main.gs.ts")
3946
+ content, err := os.ReadFile(outputFile)
3947
+ if err != nil {
3948
+ t.Fatal(err.Error())
3949
+ }
3950
+ text := string(content)
3951
+ if !strings.Contains(text, "let current: Reader | null = r") {
3952
+ t.Fatalf("missing interface short declaration annotation:\n%s", text)
3953
+ }
3954
+ }
3955
+
2605
3956
  func TestCompilePackagesPropagatesImmediateFuncLitAsync(t *testing.T) {
2606
3957
  moduleDir := writePackageGraphFixture(t, map[string]string{
2607
3958
  "go.mod": "module example.test/immediate-func-lit-async\n\ngo 1.25.3\n",
@@ -2972,7 +4323,7 @@ func TestCompilePackagesLowersUnaryBitwiseComplement(t *testing.T) {
2972
4323
  }
2973
4324
  text := string(content)
2974
4325
  for _, want := range []string{
2975
- "mask = mask & ~(3)",
4326
+ "mask = mask & ~((3))",
2976
4327
  "$.println(~value, value & ~(3), mask, 0o700)",
2977
4328
  } {
2978
4329
  if !strings.Contains(text, want) {
@@ -3098,6 +4449,41 @@ func TestCompilePackagesUnwrapsImportedVarRefValueMethodReceiver(t *testing.T) {
3098
4449
  }
3099
4450
  }
3100
4451
 
4452
+ func TestCompilePackagesUnwrapsOverridePointerMethodReceiver(t *testing.T) {
4453
+ moduleDir := writePackageGraphFixture(t, map[string]string{
4454
+ "go.mod": "module example.test/override-pointer-receiver\n\ngo 1.25.3\n",
4455
+ "main.go": strings.Join([]string{
4456
+ "package main",
4457
+ "import \"sync/atomic\"",
4458
+ "func Read(active *atomic.Int32) int32 {",
4459
+ " return active.Load()",
4460
+ "}",
4461
+ "",
4462
+ }, "\n"),
4463
+ })
4464
+ outputDir := filepath.Join(t.TempDir(), "output")
4465
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
4466
+ if err != nil {
4467
+ t.Fatal(err.Error())
4468
+ }
4469
+
4470
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
4471
+ t.Fatal(err.Error())
4472
+ }
4473
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "override-pointer-receiver", "main.gs.ts")
4474
+ content, err := os.ReadFile(outputFile)
4475
+ if err != nil {
4476
+ t.Fatal(err.Error())
4477
+ }
4478
+ text := string(content)
4479
+ if !strings.Contains(text, "atomic.Int32.prototype.Load.call($.pointerValue<atomic.Int32>(active))") {
4480
+ t.Fatalf("override pointer receiver was not unwrapped:\n%s", text)
4481
+ }
4482
+ if strings.Contains(text, "atomic.Int32.prototype.Load.call(active)") {
4483
+ t.Fatalf("override pointer receiver stayed wrapped:\n%s", text)
4484
+ }
4485
+ }
4486
+
3101
4487
  func TestCompilePackagesUnwrapsImportedArrayPackageVarReads(t *testing.T) {
3102
4488
  moduleDir := writePackageGraphFixture(t, map[string]string{
3103
4489
  "go.mod": "module example.test/imported-array-var\n\ngo 1.25.3\n",