goscript 0.2.2 → 0.2.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 (160) hide show
  1. package/compiler/gotest/testdata/browserapi/browserapi_test.go +36 -0
  2. package/compiler/lowering.go +279 -16
  3. package/compiler/override-registry_test.go +175 -0
  4. package/compiler/protobuf-ts-binding.go +154 -6
  5. package/compiler/protobuf-ts-binding_test.go +7 -2
  6. package/compiler/runtime-contract.go +2 -0
  7. package/compiler/runtime-contract_test.go +1 -0
  8. package/compiler/semantic-model.go +16 -0
  9. package/compiler/semantic-model_test.go +38 -0
  10. package/compiler/skeleton_test.go +522 -17
  11. package/compiler/typescript-emitter.go +4 -0
  12. package/dist/gs/builtin/builtin.js +7 -9
  13. package/dist/gs/builtin/builtin.js.map +1 -1
  14. package/dist/gs/builtin/defer.js +2 -2
  15. package/dist/gs/builtin/hostio.js +5 -5
  16. package/dist/gs/builtin/hostio.js.map +1 -1
  17. package/dist/gs/builtin/map.js +2 -1
  18. package/dist/gs/builtin/map.js.map +1 -1
  19. package/dist/gs/builtin/slice.d.ts +3 -0
  20. package/dist/gs/builtin/slice.js +39 -0
  21. package/dist/gs/builtin/slice.js.map +1 -1
  22. package/dist/gs/builtin/type.js +49 -0
  23. package/dist/gs/builtin/type.js.map +1 -1
  24. package/dist/gs/compress/gzip/index.d.ts +41 -0
  25. package/dist/gs/compress/gzip/index.js +235 -0
  26. package/dist/gs/compress/gzip/index.js.map +1 -0
  27. package/dist/gs/compress/zlib/index.js +5 -2
  28. package/dist/gs/compress/zlib/index.js.map +1 -1
  29. package/dist/gs/crypto/ecdh/index.js +27 -8
  30. package/dist/gs/crypto/ecdh/index.js.map +1 -1
  31. package/dist/gs/crypto/ed25519/index.js +3 -3
  32. package/dist/gs/crypto/ed25519/index.js.map +1 -1
  33. package/dist/gs/crypto/rand/index.js +6 -3
  34. package/dist/gs/crypto/rand/index.js.map +1 -1
  35. package/dist/gs/embed/index.js +9 -3
  36. package/dist/gs/embed/index.js.map +1 -1
  37. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  38. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +33 -0
  39. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  40. package/dist/gs/github.com/mr-tron/base58/base58/index.js +4 -1
  41. package/dist/gs/github.com/mr-tron/base58/base58/index.js.map +1 -1
  42. package/dist/gs/golang.org/x/crypto/scrypt/index.d.ts +2 -0
  43. package/dist/gs/golang.org/x/crypto/scrypt/index.js +39 -0
  44. package/dist/gs/golang.org/x/crypto/scrypt/index.js.map +1 -0
  45. package/dist/gs/hash/fnv/index.js +13 -5
  46. package/dist/gs/hash/fnv/index.js.map +1 -1
  47. package/dist/gs/io/fs/glob.d.ts +3 -3
  48. package/dist/gs/io/fs/glob.js +9 -9
  49. package/dist/gs/io/fs/glob.js.map +1 -1
  50. package/dist/gs/io/fs/readdir.d.ts +2 -2
  51. package/dist/gs/io/fs/readdir.js +13 -74
  52. package/dist/gs/io/fs/readdir.js.map +1 -1
  53. package/dist/gs/io/fs/readlink.d.ts +1 -1
  54. package/dist/gs/io/fs/readlink.js +2 -2
  55. package/dist/gs/io/fs/readlink.js.map +1 -1
  56. package/dist/gs/io/fs/stat.d.ts +4 -2
  57. package/dist/gs/io/fs/stat.js +12 -73
  58. package/dist/gs/io/fs/stat.js.map +1 -1
  59. package/dist/gs/io/fs/sub.d.ts +2 -2
  60. package/dist/gs/io/fs/sub.js +11 -11
  61. package/dist/gs/io/fs/sub.js.map +1 -1
  62. package/dist/gs/io/fs/walk.js +2 -2
  63. package/dist/gs/io/fs/walk.js.map +1 -1
  64. package/dist/gs/maps/iter.js.map +1 -1
  65. package/dist/gs/maps/maps.js.map +1 -1
  66. package/dist/gs/mime/index.js +5 -2
  67. package/dist/gs/mime/index.js.map +1 -1
  68. package/dist/gs/net/http/httptest/index.js +6 -3
  69. package/dist/gs/net/http/httptest/index.js.map +1 -1
  70. package/dist/gs/net/http/index.d.ts +34 -18
  71. package/dist/gs/net/http/index.js +280 -63
  72. package/dist/gs/net/http/index.js.map +1 -1
  73. package/dist/gs/net/http/pprof/index.d.ts +5 -5
  74. package/dist/gs/net/http/pprof/index.js +21 -21
  75. package/dist/gs/net/http/pprof/index.js.map +1 -1
  76. package/dist/gs/reflect/iter.js +1 -1
  77. package/dist/gs/reflect/iter.js.map +1 -1
  78. package/dist/gs/reflect/type.d.ts +2 -0
  79. package/dist/gs/reflect/type.js +53 -21
  80. package/dist/gs/reflect/type.js.map +1 -1
  81. package/dist/gs/runtime/pprof/index.js.map +1 -1
  82. package/dist/gs/runtime/runtime.js +2 -2
  83. package/dist/gs/runtime/runtime.js.map +1 -1
  84. package/dist/gs/runtime/trace/index.js.map +1 -1
  85. package/dist/gs/slices/slices.d.ts +1 -1
  86. package/dist/gs/slices/slices.js +37 -4
  87. package/dist/gs/slices/slices.js.map +1 -1
  88. package/gs/builtin/builtin.ts +11 -14
  89. package/gs/builtin/defer.ts +2 -2
  90. package/gs/builtin/hostio.ts +5 -5
  91. package/gs/builtin/map.ts +4 -1
  92. package/gs/builtin/runtime-contract.test.ts +25 -0
  93. package/gs/builtin/slice.test.ts +14 -0
  94. package/gs/builtin/slice.ts +64 -0
  95. package/gs/builtin/type.ts +72 -0
  96. package/gs/bytes/bytes.test.ts +14 -13
  97. package/gs/compress/gzip/index.test.ts +86 -0
  98. package/gs/compress/gzip/index.ts +297 -0
  99. package/gs/compress/gzip/meta.json +6 -0
  100. package/gs/compress/gzip/parity.json +45 -0
  101. package/gs/compress/zlib/index.test.ts +19 -5
  102. package/gs/compress/zlib/index.ts +16 -7
  103. package/gs/context/context.test.ts +3 -1
  104. package/gs/crypto/ecdh/index.test.ts +6 -2
  105. package/gs/crypto/ecdh/index.ts +49 -12
  106. package/gs/crypto/ed25519/index.ts +20 -7
  107. package/gs/crypto/rand/index.ts +6 -3
  108. package/gs/embed/index.test.ts +4 -4
  109. package/gs/embed/index.ts +9 -3
  110. package/gs/fmt/fmt.test.ts +29 -4
  111. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +126 -0
  112. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +46 -0
  113. package/gs/github.com/mr-tron/base58/base58/index.ts +9 -3
  114. package/gs/github.com/zeebo/blake3/internal/consts/index.test.ts +2 -8
  115. package/gs/golang.org/x/crypto/scrypt/index.test.ts +81 -0
  116. package/gs/golang.org/x/crypto/scrypt/index.ts +54 -0
  117. package/gs/golang.org/x/crypto/scrypt/meta.json +5 -0
  118. package/gs/hash/fnv/index.test.ts +1 -8
  119. package/gs/hash/fnv/index.ts +27 -10
  120. package/gs/io/fs/glob.ts +14 -11
  121. package/gs/io/fs/meta.json +5 -0
  122. package/gs/io/fs/readdir.test.ts +63 -2
  123. package/gs/io/fs/readdir.ts +33 -30
  124. package/gs/io/fs/readlink.test.ts +2 -2
  125. package/gs/io/fs/readlink.ts +5 -2
  126. package/gs/io/fs/stat.test.ts +79 -0
  127. package/gs/io/fs/stat.ts +24 -10
  128. package/gs/io/fs/sub.test.ts +93 -0
  129. package/gs/io/fs/sub.ts +13 -13
  130. package/gs/io/fs/walk.ts +2 -2
  131. package/gs/maps/iter.ts +9 -9
  132. package/gs/maps/maps.ts +4 -4
  133. package/gs/math/bits/index.test.ts +10 -1
  134. package/gs/mime/index.test.ts +33 -15
  135. package/gs/mime/index.ts +9 -2
  136. package/gs/net/http/httptest/index.test.ts +17 -3
  137. package/gs/net/http/httptest/index.ts +8 -3
  138. package/gs/net/http/index.test.ts +851 -124
  139. package/gs/net/http/index.ts +612 -146
  140. package/gs/net/http/meta.json +3 -1
  141. package/gs/net/http/pprof/index.test.ts +4 -4
  142. package/gs/net/http/pprof/index.ts +43 -22
  143. package/gs/os/file_unix_js.test.ts +22 -0
  144. package/gs/reflect/iter.ts +4 -2
  145. package/gs/reflect/map.test.ts +56 -1
  146. package/gs/reflect/type.ts +76 -37
  147. package/gs/runtime/pprof/index.test.ts +7 -1
  148. package/gs/runtime/pprof/index.ts +5 -1
  149. package/gs/runtime/runtime.test.ts +7 -0
  150. package/gs/runtime/runtime.ts +2 -4
  151. package/gs/runtime/trace/index.test.ts +9 -1
  152. package/gs/runtime/trace/index.ts +5 -1
  153. package/gs/slices/meta.json +3 -0
  154. package/gs/slices/slices.test.ts +59 -21
  155. package/gs/slices/slices.ts +61 -20
  156. package/gs/strconv/complex.test.ts +17 -3
  157. package/gs/sync/atomic/doc_64.test.ts +2 -9
  158. package/gs/sync/sync.test.ts +18 -8
  159. package/gs/syscall/js/index.test.ts +9 -4
  160. package/package.json +5 -4
@@ -488,6 +488,181 @@ func TestCompilePackagesAwaitsOverrideAsyncFunctions(t *testing.T) {
488
488
  }
489
489
  }
490
490
 
491
+ func TestCompilePackagesAwaitsIOFSStatOverride(t *testing.T) {
492
+ moduleDir := writePackageGraphFixture(t, map[string]string{
493
+ "go.mod": "module example.test/iofsstat\n\ngo 1.25.3\n",
494
+ "main.go": strings.Join([]string{
495
+ "package main",
496
+ "import \"io/fs\"",
497
+ "type memFS struct{}",
498
+ "func (memFS) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist }",
499
+ "func Use(fsys fs.FS) error {",
500
+ " _, err := fs.Stat(fsys, \"pkg\")",
501
+ " return err",
502
+ "}",
503
+ "func main() { _ = Use(memFS{}) }",
504
+ "",
505
+ }, "\n"),
506
+ })
507
+ out := filepath.Join(t.TempDir(), "out")
508
+ comp, err := NewCompiler(&Config{
509
+ Dir: moduleDir,
510
+ OutputPath: out,
511
+ AllDependencies: true,
512
+ }, nil, nil)
513
+ if err != nil {
514
+ t.Fatal(err.Error())
515
+ }
516
+
517
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
518
+ t.Fatal(err.Error())
519
+ }
520
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "iofsstat", "main.gs.ts"))
521
+ if err != nil {
522
+ t.Fatal(err.Error())
523
+ }
524
+ text := string(content)
525
+ if !strings.Contains(text, "export async function Use(") {
526
+ t.Fatalf("fs.Stat caller was not marked async:\n%s", text)
527
+ }
528
+ if !strings.Contains(text, "await fs.Stat(") {
529
+ t.Fatalf("io/fs Stat override call was not awaited:\n%s", text)
530
+ }
531
+ }
532
+
533
+ func TestCompilePackagesAwaitsIOFSLstatOverride(t *testing.T) {
534
+ moduleDir := writePackageGraphFixture(t, map[string]string{
535
+ "go.mod": "module example.test/iofslstat\n\ngo 1.25.3\n",
536
+ "main.go": strings.Join([]string{
537
+ "package main",
538
+ "import \"io/fs\"",
539
+ "type memFS struct{}",
540
+ "func (memFS) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist }",
541
+ "func Use(fsys fs.FS) error {",
542
+ " _, err := fs.Lstat(fsys, \"pkg\")",
543
+ " return err",
544
+ "}",
545
+ "func main() { _ = Use(memFS{}) }",
546
+ "",
547
+ }, "\n"),
548
+ })
549
+ out := filepath.Join(t.TempDir(), "out")
550
+ comp, err := NewCompiler(&Config{
551
+ Dir: moduleDir,
552
+ OutputPath: out,
553
+ AllDependencies: true,
554
+ }, nil, nil)
555
+ if err != nil {
556
+ t.Fatal(err.Error())
557
+ }
558
+
559
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
560
+ t.Fatal(err.Error())
561
+ }
562
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "iofslstat", "main.gs.ts"))
563
+ if err != nil {
564
+ t.Fatal(err.Error())
565
+ }
566
+ text := string(content)
567
+ if !strings.Contains(text, "export async function Use(") {
568
+ t.Fatalf("fs.Lstat caller was not marked async:\n%s", text)
569
+ }
570
+ if !strings.Contains(text, "await fs.Lstat(") {
571
+ t.Fatalf("io/fs Lstat override call was not awaited:\n%s", text)
572
+ }
573
+ }
574
+
575
+ func TestCompilePackagesAwaitsIOFSSubOverride(t *testing.T) {
576
+ moduleDir := writePackageGraphFixture(t, map[string]string{
577
+ "go.mod": "module example.test/iofssub\n\ngo 1.25.3\n",
578
+ "main.go": strings.Join([]string{
579
+ "package main",
580
+ "import \"io/fs\"",
581
+ "type memFS struct{}",
582
+ "func (memFS) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist }",
583
+ "func Use(fsys fs.FS) (fs.FS, error) {",
584
+ " return fs.Sub(fsys, \"pkg\")",
585
+ "}",
586
+ "func main() { _, _ = Use(memFS{}) }",
587
+ "",
588
+ }, "\n"),
589
+ })
590
+ out := filepath.Join(t.TempDir(), "out")
591
+ comp, err := NewCompiler(&Config{
592
+ Dir: moduleDir,
593
+ OutputPath: out,
594
+ AllDependencies: true,
595
+ }, nil, nil)
596
+ if err != nil {
597
+ t.Fatal(err.Error())
598
+ }
599
+
600
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
601
+ t.Fatal(err.Error())
602
+ }
603
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "iofssub", "main.gs.ts"))
604
+ if err != nil {
605
+ t.Fatal(err.Error())
606
+ }
607
+ text := string(content)
608
+ if !strings.Contains(text, "export async function Use(") {
609
+ t.Fatalf("fs.Sub caller was not marked async:\n%s", text)
610
+ }
611
+ if !strings.Contains(text, "return await fs.Sub(") {
612
+ t.Fatalf("io/fs Sub override call was not awaited:\n%s", text)
613
+ }
614
+ }
615
+
616
+ func TestCompilePackagesAwaitsAsyncSlicesSortFuncComparator(t *testing.T) {
617
+ moduleDir := writePackageGraphFixture(t, map[string]string{
618
+ "go.mod": "module example.test/slicesasyncsort\n\ngo 1.25.3\n",
619
+ "main.go": strings.Join([]string{
620
+ "package main",
621
+ "import \"slices\"",
622
+ "type Comparator interface { Less(a, b int) bool }",
623
+ "func Use(c Comparator, values []int) {",
624
+ " slices.SortFunc(values, func(a, b int) int {",
625
+ " if c.Less(a, b) {",
626
+ " return -1",
627
+ " }",
628
+ " if c.Less(b, a) {",
629
+ " return 1",
630
+ " }",
631
+ " return 0",
632
+ " })",
633
+ "}",
634
+ "",
635
+ }, "\n"),
636
+ })
637
+ out := filepath.Join(t.TempDir(), "out")
638
+ comp, err := NewCompiler(&Config{
639
+ Dir: moduleDir,
640
+ OutputPath: out,
641
+ AllDependencies: true,
642
+ }, nil, nil)
643
+ if err != nil {
644
+ t.Fatal(err.Error())
645
+ }
646
+
647
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
648
+ t.Fatal(err.Error())
649
+ }
650
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "slicesasyncsort", "main.gs.ts"))
651
+ if err != nil {
652
+ t.Fatal(err.Error())
653
+ }
654
+ text := string(content)
655
+ if !strings.Contains(text, "export async function Use") {
656
+ t.Fatalf("caller was not marked async for slices.SortFunc:\n%s", text)
657
+ }
658
+ if !strings.Contains(text, "await slices.SortFunc") {
659
+ t.Fatalf("slices.SortFunc call was not awaited:\n%s", text)
660
+ }
661
+ if !strings.Contains(text, "$.functionValue(async") {
662
+ t.Fatalf("SortFunc comparator was not lowered as async:\n%s", text)
663
+ }
664
+ }
665
+
491
666
  func TestCompilePackagesAwaitsReflectValueCall(t *testing.T) {
492
667
  moduleDir := writePackageGraphFixture(t, map[string]string{
493
668
  "go.mod": "module example.test/reflectcallasync\n\ngo 1.25.3\n",
@@ -5,6 +5,7 @@ import (
5
5
  "go/ast"
6
6
  "os"
7
7
  "path/filepath"
8
+ "slices"
8
9
  "strings"
9
10
  )
10
11
 
@@ -16,6 +17,13 @@ type protobufTypeScriptBinding struct {
16
17
  hasOneof bool
17
18
  }
18
19
 
20
+ type protobufTypeScriptBindingOneofCase struct {
21
+ groupLocalName string
22
+ caseLocalName string
23
+ branchCtor string
24
+ valueCtor string
25
+ }
26
+
19
27
  func protobufTypeScriptBindings(semPkg *semanticPackage, options LoweringOptions) (map[string]protobufTypeScriptBinding, []Diagnostic) {
20
28
  if semPkg == nil || semPkg.source == nil || !options.ProtobufTypeScriptBinding {
21
29
  return nil, nil
@@ -291,6 +299,7 @@ func rewriteProtobufTypeScriptBindingFile(file *loweredFile, binding protobufTyp
291
299
  source: binding.importSource,
292
300
  sideEffect: true,
293
301
  })
302
+ oneofCases := protobufTypeScriptBindingOneofCases(file)
294
303
  var setupDecls []loweredDecl
295
304
  for _, decl := range file.decls {
296
305
  if decl.structType == nil {
@@ -306,7 +315,7 @@ func rewriteProtobufTypeScriptBindingFile(file *loweredFile, binding protobufTyp
306
315
  if !ok {
307
316
  continue
308
317
  }
309
- setup := protobufTypeScriptBindingStructSetupDecl(decl.structType, importAlias, messageName)
318
+ setup := protobufTypeScriptBindingStructSetupDecl(decl.structType, importAlias, messageName, oneofCases[decl.structType.name])
310
319
  if setup.code != "" {
311
320
  setupDecls = append(setupDecls, setup)
312
321
  }
@@ -314,6 +323,102 @@ func rewriteProtobufTypeScriptBindingFile(file *loweredFile, binding protobufTyp
314
323
  file.decls = append(file.decls, setupDecls...)
315
324
  }
316
325
 
326
+ func protobufTypeScriptBindingOneofCases(file *loweredFile) map[string][]protobufTypeScriptBindingOneofCase {
327
+ parentByName := make(map[string]*loweredStruct)
328
+ for _, decl := range file.decls {
329
+ if decl.structType != nil {
330
+ parentByName[decl.structType.name] = decl.structType
331
+ }
332
+ }
333
+ out := make(map[string][]protobufTypeScriptBindingOneofCase)
334
+ for _, decl := range file.decls {
335
+ branch := decl.structType
336
+ if branch == nil || !strings.Contains(branch.name, "_") {
337
+ continue
338
+ }
339
+ parent := protobufTypeScriptBindingOneofParent(branch.name, parentByName)
340
+ if parent == nil {
341
+ continue
342
+ }
343
+ groups := protobufTypeScriptBindingOneofGroups(parent)
344
+ if len(groups) == 0 {
345
+ continue
346
+ }
347
+ groupLocalName := protobufTypeScriptBindingOneofCaseGroup(branch, parent, groups)
348
+ if groupLocalName == "" {
349
+ continue
350
+ }
351
+ for _, field := range branch.fields {
352
+ if !strings.Contains(field.tag, "oneof") {
353
+ continue
354
+ }
355
+ out[parent.name] = append(out[parent.name], protobufTypeScriptBindingOneofCase{
356
+ groupLocalName: groupLocalName,
357
+ caseLocalName: protobufTypeScriptBindingFieldLocalName(field),
358
+ branchCtor: branch.name,
359
+ valueCtor: protobufTypeScriptBindingFieldCtor(field),
360
+ })
361
+ }
362
+ }
363
+ for parentName := range out {
364
+ slices.SortFunc(out[parentName], func(left, right protobufTypeScriptBindingOneofCase) int {
365
+ if left.groupLocalName != right.groupLocalName {
366
+ return strings.Compare(left.groupLocalName, right.groupLocalName)
367
+ }
368
+ return strings.Compare(left.caseLocalName, right.caseLocalName)
369
+ })
370
+ }
371
+ return out
372
+ }
373
+
374
+ func protobufTypeScriptBindingOneofParent(branchName string, parents map[string]*loweredStruct) *loweredStruct {
375
+ var parent *loweredStruct
376
+ for name, candidate := range parents {
377
+ if name == branchName || !strings.HasPrefix(branchName, name+"_") {
378
+ continue
379
+ }
380
+ if parent == nil || len(name) > len(parent.name) {
381
+ parent = candidate
382
+ }
383
+ }
384
+ return parent
385
+ }
386
+
387
+ func protobufTypeScriptBindingOneofGroups(parent *loweredStruct) map[string]string {
388
+ groups := make(map[string]string)
389
+ for _, field := range parent.fields {
390
+ if !strings.Contains(field.tag, "protobuf_oneof") {
391
+ continue
392
+ }
393
+ localName := protobufTypeScriptBindingTagValue(field.tag, "protobuf_oneof:\"")
394
+ if localName == "" {
395
+ groups[field.name] = protobufTypeScriptBindingFieldLocalName(field)
396
+ continue
397
+ }
398
+ groups[field.name] = protobufTypeScriptBindingProtoCamel(localName)
399
+ }
400
+ return groups
401
+ }
402
+
403
+ func protobufTypeScriptBindingOneofCaseGroup(branch, parent *loweredStruct, groups map[string]string) string {
404
+ if len(groups) == 1 {
405
+ for _, localName := range groups {
406
+ return localName
407
+ }
408
+ }
409
+ prefix := "is" + parent.name + "_"
410
+ for _, method := range branch.methods {
411
+ groupName, ok := strings.CutPrefix(method.name, prefix)
412
+ if !ok {
413
+ continue
414
+ }
415
+ if localName := groups[groupName]; localName != "" {
416
+ return localName
417
+ }
418
+ }
419
+ return ""
420
+ }
421
+
317
422
  func protobufTypeScriptBindingSyntheticMapEntry(name string) bool {
318
423
  return strings.Contains(name, "_") && strings.HasSuffix(name, "Entry")
319
424
  }
@@ -466,23 +571,66 @@ func protobufBindingParam(method *loweredFunction, idx int, fallback string) str
466
571
  return method.params[idx].name
467
572
  }
468
573
 
469
- func protobufTypeScriptBindingStructSetupDecl(structType *loweredStruct, importAlias, messageName string) loweredDecl {
574
+ func protobufTypeScriptBindingStructSetupDecl(structType *loweredStruct, importAlias, messageName string, oneofCases []protobufTypeScriptBindingOneofCase) loweredDecl {
470
575
  if structType == nil {
471
576
  return loweredDecl{}
472
577
  }
473
578
  if messageName == "" {
474
579
  messageName = structType.name
475
580
  }
476
- entries := make([]string, 0, len(structType.fields))
581
+ fieldEntries := make(map[string]string)
477
582
  for _, field := range structType.fields {
478
583
  ctor := protobufTypeScriptBindingFieldCtor(field)
479
584
  if ctor == "" {
480
585
  continue
481
586
  }
482
- entries = append(entries, strconvQuote(protobufTypeScriptBindingFieldLocalName(field))+": "+ctor)
587
+ fieldEntries[protobufTypeScriptBindingFieldLocalName(field)] = ctor
588
+ }
589
+ for _, oneofCase := range oneofCases {
590
+ if oneofCase.valueCtor != "" {
591
+ fieldEntries[oneofCase.caseLocalName] = oneofCase.valueCtor
592
+ }
593
+ }
594
+ fieldNames := make([]string, 0, len(fieldEntries))
595
+ for name := range fieldEntries {
596
+ fieldNames = append(fieldNames, name)
597
+ }
598
+ slices.Sort(fieldNames)
599
+ entries := make([]string, 0, len(fieldNames))
600
+ for _, name := range fieldNames {
601
+ entries = append(entries, strconvQuote(name)+": "+fieldEntries[name])
602
+ }
603
+ code := "(" + structType.name + " as any).__protobufTypeScriptMessage = " + importAlias + "." + messageName + ";\n" +
604
+ "(" + structType.name + " as any).__protobufTypeScriptFields = {" + strings.Join(entries, ", ") + "};"
605
+ if len(oneofCases) != 0 {
606
+ code += "\n(" + structType.name + " as any).__protobufTypeScriptOneofFields = " + protobufTypeScriptBindingOneofFieldsLiteral(oneofCases) + ";"
607
+ }
608
+ return loweredDecl{code: code}
609
+ }
610
+
611
+ func protobufTypeScriptBindingOneofFieldsLiteral(oneofCases []protobufTypeScriptBindingOneofCase) string {
612
+ groups := make(map[string][]protobufTypeScriptBindingOneofCase)
613
+ for _, oneofCase := range oneofCases {
614
+ groups[oneofCase.groupLocalName] = append(groups[oneofCase.groupLocalName], oneofCase)
615
+ }
616
+ groupNames := make([]string, 0, len(groups))
617
+ for name := range groups {
618
+ groupNames = append(groupNames, name)
619
+ }
620
+ slices.Sort(groupNames)
621
+ entries := make([]string, 0, len(groupNames))
622
+ for _, groupName := range groupNames {
623
+ cases := groups[groupName]
624
+ slices.SortFunc(cases, func(left, right protobufTypeScriptBindingOneofCase) int {
625
+ return strings.Compare(left.caseLocalName, right.caseLocalName)
626
+ })
627
+ caseEntries := make([]string, 0, len(cases))
628
+ for _, oneofCase := range cases {
629
+ caseEntries = append(caseEntries, strconvQuote(oneofCase.caseLocalName)+": "+oneofCase.branchCtor)
630
+ }
631
+ entries = append(entries, strconvQuote(groupName)+": {"+strings.Join(caseEntries, ", ")+"}")
483
632
  }
484
- return loweredDecl{code: "(" + structType.name + " as any).__protobufTypeScriptMessage = " + importAlias + "." + messageName + ";\n" +
485
- "(" + structType.name + " as any).__protobufTypeScriptFields = {" + strings.Join(entries, ", ") + "};"}
633
+ return "{" + strings.Join(entries, ", ") + "}"
486
634
  }
487
635
 
488
636
  func protobufTypeScriptBindingFieldLocalName(field loweredStructField) string {
@@ -204,7 +204,11 @@ func TestProtobufTypeScriptBindingEmitsMetadataForPreservedOneofFiles(t *testing
204
204
  "type Wrapper_StringValue struct {\n"+
205
205
  "\tStringValue string `protobuf:\"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof\"`\n"+
206
206
  "}\n\n"+
207
- "func (*Wrapper_StringValue) isWrapper_Choice() {}\n")
207
+ "func (*Wrapper_StringValue) isWrapper_Choice() {}\n\n"+
208
+ "type Wrapper_InnerValue struct {\n"+
209
+ "\tInnerValue *Inner `protobuf:\"bytes,3,opt,name=inner_value,json=innerValue,proto3,oneof\"`\n"+
210
+ "}\n\n"+
211
+ "func (*Wrapper_InnerValue) isWrapper_Choice() {}\n")
208
212
  writeTestFile(t, dir, "foo.pb.ts", `export interface Inner {
209
213
  name?: string
210
214
  }
@@ -231,7 +235,8 @@ export const Wrapper = {} as any
231
235
  binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "oneofpb", "foo.pb.ts"))
232
236
  if !strings.Contains(binding, `import * as __protobuf_ts`) ||
233
237
  !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptMessage = __protobuf_ts.Wrapper;`) ||
234
- !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptFields = {"inner": Inner};`) {
238
+ !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptFields = {"inner": Inner, "innerValue": Inner};`) ||
239
+ !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptOneofFields = {"choice": {"innerValue": Wrapper_InnerValue, "stringValue": Wrapper_StringValue}};`) {
235
240
  t.Fatalf("oneof-preserved protobuf file should still expose TypeScript metadata, got:\n%s", binding)
236
241
  }
237
242
  if strings.Contains(binding, `__protobuf_ts.Wrapper_StringValue`) {
@@ -88,6 +88,7 @@ const (
88
88
  RuntimeHelperSliceToArray RuntimeHelper = "slice.sliceToArray"
89
89
  RuntimeHelperSliceToArrayPointer RuntimeHelper = "slice.sliceToArrayPointer"
90
90
  RuntimeHelperAppend RuntimeHelper = "slice.append"
91
+ RuntimeHelperAppendSlice RuntimeHelper = "slice.appendSlice"
91
92
  RuntimeHelperCopy RuntimeHelper = "slice.copy"
92
93
  RuntimeHelperAsArray RuntimeHelper = "slice.asArray"
93
94
  RuntimeHelperStringToRunes RuntimeHelper = "slice.stringToRunes"
@@ -318,6 +319,7 @@ func runtimeHelperContracts() []RuntimeHelperContract {
318
319
  runtimeHelper(RuntimeHelperSliceToArray, "sliceToArray", RuntimeHelperCategorySlice),
319
320
  runtimeHelper(RuntimeHelperSliceToArrayPointer, "sliceToArrayPointer", RuntimeHelperCategorySlice),
320
321
  runtimeHelper(RuntimeHelperAppend, "append", RuntimeHelperCategorySlice),
322
+ runtimeHelper(RuntimeHelperAppendSlice, "appendSlice", RuntimeHelperCategorySlice),
321
323
  runtimeHelper(RuntimeHelperCopy, "copy", RuntimeHelperCategorySlice),
322
324
  runtimeHelper(RuntimeHelperAsArray, "asArray", RuntimeHelperCategorySlice),
323
325
  runtimeHelper(RuntimeHelperStringToRunes, "stringToRunes", RuntimeHelperCategorySlice),
@@ -24,6 +24,7 @@ func TestRuntimeContractOwnsBuiltinImportAndHelpers(t *testing.T) {
24
24
  RuntimeHelperPointerValue: RuntimeHelperCategoryValue,
25
25
  RuntimeHelperMakeSlice: RuntimeHelperCategorySlice,
26
26
  RuntimeHelperAppend: RuntimeHelperCategorySlice,
27
+ RuntimeHelperAppendSlice: RuntimeHelperCategorySlice,
27
28
  RuntimeHelperIndexAddress: RuntimeHelperCategorySlice,
28
29
  RuntimeHelperIndexByteAddress: RuntimeHelperCategorySlice,
29
30
  RuntimeHelperUnsafePointerRef: RuntimeHelperCategorySlice,
@@ -769,6 +769,9 @@ func (o *SemanticModelOwner) collectFunctionFacts(
769
769
  if callUsesFunctionIdentifier(pkg, typed.Fun) {
770
770
  markFunctionAsync(semFn, "function-identifier-call")
771
771
  }
772
+ if callUsesInterfaceMethod(pkg, typed.Fun) {
773
+ markFunctionAsync(semFn, "interface-method-call")
774
+ }
772
775
  if overrideFacts.IsMethodAsync(overrideCallPackage(pkg, typed.Fun), overrideCallMethod(pkg, typed.Fun)) {
773
776
  markFunctionAsync(semFn, "override")
774
777
  }
@@ -815,6 +818,9 @@ func recordImmediateFuncLitAsyncFacts(
815
818
  if callUsesFunctionIdentifier(pkg, typed.Fun) {
816
819
  markFunctionAsync(semFn, "async-function-literal-call")
817
820
  }
821
+ if callUsesInterfaceMethod(pkg, typed.Fun) {
822
+ markFunctionAsync(semFn, "async-function-literal-call")
823
+ }
818
824
  if called != nil {
819
825
  calledFn := semanticFunctionFor(model, called)
820
826
  if calledFn != nil && calledFn.async {
@@ -1156,6 +1162,10 @@ func exprMayNeedAwait(model *SemanticModel, pkg *packages.Package, expr ast.Expr
1156
1162
  needsAwait = true
1157
1163
  return false
1158
1164
  }
1165
+ if callUsesInterfaceMethod(pkg, typed.Fun) {
1166
+ needsAwait = true
1167
+ return false
1168
+ }
1159
1169
  if called := calledFunction(pkg, typed.Fun); called != nil {
1160
1170
  semFn := semanticFunctionFor(model, called)
1161
1171
  if semFn != nil && semFn.async {
@@ -1672,6 +1682,9 @@ func (o *SemanticModelOwner) applyInterfaceAsyncMethods(
1672
1682
  }
1673
1683
  for methodName, ifaceMethod := range graphEntry.ifaceMethods {
1674
1684
  implMethod := graphEntry.implMethods[methodName]
1685
+ if isSyncErrorMethodFunc(ifaceMethod) || isSyncErrorMethodFunc(implMethod) {
1686
+ continue
1687
+ }
1675
1688
  implFn := semanticFunctionFor(model, implMethod)
1676
1689
  if implFn != nil && implFn.async {
1677
1690
  model.markInterfaceMethodAsync(ifaceMethod)
@@ -1697,6 +1710,9 @@ func (o *SemanticModelOwner) applyAnonymousInterfaceAsyncMethods(
1697
1710
  }
1698
1711
  for methodName, ifaceMethod := range graphEntry.ifaceMethods {
1699
1712
  implMethod := graphEntry.implMethods[methodName]
1713
+ if isSyncErrorMethodFunc(ifaceMethod) || isSyncErrorMethodFunc(implMethod) {
1714
+ continue
1715
+ }
1700
1716
  if model.functionAsync(implMethod) {
1701
1717
  model.markInterfaceMethodAsync(ifaceMethod)
1702
1718
  }
@@ -604,6 +604,44 @@ func TestSemanticModelMarksFunctionIdentifierCallAsync(t *testing.T) {
604
604
  }
605
605
  }
606
606
 
607
+ func TestSemanticModelMarksInterfaceMethodCallAsync(t *testing.T) {
608
+ moduleDir := writePackageGraphFixture(t, map[string]string{
609
+ "go.mod": "module example.test/interfacecall\n\ngo 1.25.3\n",
610
+ "iface/controller.go": strings.Join([]string{
611
+ "package iface",
612
+ "import \"context\"",
613
+ "type Controller interface {",
614
+ " Execute(context.Context) error",
615
+ "}",
616
+ "",
617
+ }, "\n"),
618
+ "controller.go": strings.Join([]string{
619
+ "package interfacecall",
620
+ "import (",
621
+ " \"context\"",
622
+ " \"example.test/interfacecall/iface\"",
623
+ ")",
624
+ "func Run(ctx context.Context, c iface.Controller) error {",
625
+ " return c.Execute(ctx)",
626
+ "}",
627
+ "",
628
+ }, "\n"),
629
+ })
630
+ graph := loadPackageGraph(t, &CompileRequest{
631
+ Patterns: []string{".", "./iface"},
632
+ Dir: moduleDir,
633
+ OutputPath: filepath.Join(t.TempDir(), "out"),
634
+ DependencyMode: DependencyModeAll,
635
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
636
+ })
637
+ model := buildSemanticModel(t, graph)
638
+ run := requireDefinedFunc(t, graph, "example.test/interfacecall", "Run")
639
+ semFn := model.functions[run]
640
+ if semFn == nil || !semFn.async {
641
+ t.Fatalf("expected Run to be async after calling interface method, got %#v", semFn)
642
+ }
643
+ }
644
+
607
645
  func buildSemanticModel(t *testing.T, graph *PackageGraph) *SemanticModel {
608
646
  t.Helper()
609
647