goscript 0.2.1 → 0.2.3

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 (164) hide show
  1. package/compiler/gotest/runner.go +98 -0
  2. package/compiler/gotest/runner_test.go +45 -0
  3. package/compiler/gotest/testdata/browserapi/browserapi_test.go +36 -0
  4. package/compiler/lowering.go +227 -11
  5. package/compiler/override-registry_test.go +50 -0
  6. package/compiler/protobuf-ts-binding.go +155 -7
  7. package/compiler/protobuf-ts-binding_test.go +116 -2
  8. package/compiler/runtime-contract.go +2 -0
  9. package/compiler/runtime-contract_test.go +1 -0
  10. package/compiler/semantic-model.go +16 -0
  11. package/compiler/semantic-model_test.go +38 -0
  12. package/compiler/skeleton_test.go +477 -16
  13. package/compiler/typescript-emitter.go +4 -0
  14. package/dist/gs/builtin/builtin.js +7 -9
  15. package/dist/gs/builtin/builtin.js.map +1 -1
  16. package/dist/gs/builtin/defer.js +2 -2
  17. package/dist/gs/builtin/hostio.js +5 -5
  18. package/dist/gs/builtin/hostio.js.map +1 -1
  19. package/dist/gs/builtin/map.js +2 -1
  20. package/dist/gs/builtin/map.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +3 -0
  22. package/dist/gs/builtin/slice.js +39 -0
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/builtin/type.js +49 -0
  25. package/dist/gs/builtin/type.js.map +1 -1
  26. package/dist/gs/compress/zlib/index.js +5 -2
  27. package/dist/gs/compress/zlib/index.js.map +1 -1
  28. package/dist/gs/crypto/aes/index.d.ts +15 -0
  29. package/dist/gs/crypto/aes/index.js +57 -0
  30. package/dist/gs/crypto/aes/index.js.map +1 -0
  31. package/dist/gs/crypto/cipher/index.d.ts +41 -0
  32. package/dist/gs/crypto/cipher/index.js +255 -0
  33. package/dist/gs/crypto/cipher/index.js.map +1 -0
  34. package/dist/gs/crypto/ecdh/index.js +27 -8
  35. package/dist/gs/crypto/ecdh/index.js.map +1 -1
  36. package/dist/gs/crypto/ed25519/index.js +3 -3
  37. package/dist/gs/crypto/ed25519/index.js.map +1 -1
  38. package/dist/gs/crypto/rand/index.js +6 -3
  39. package/dist/gs/crypto/rand/index.js.map +1 -1
  40. package/dist/gs/embed/index.js +9 -3
  41. package/dist/gs/embed/index.js.map +1 -1
  42. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  43. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +33 -0
  44. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  45. package/dist/gs/github.com/mr-tron/base58/base58/index.js +4 -1
  46. package/dist/gs/github.com/mr-tron/base58/base58/index.js.map +1 -1
  47. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.d.ts +31 -0
  48. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js +117 -0
  49. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js.map +1 -0
  50. package/dist/gs/golang.org/x/crypto/scrypt/index.d.ts +2 -0
  51. package/dist/gs/golang.org/x/crypto/scrypt/index.js +39 -0
  52. package/dist/gs/golang.org/x/crypto/scrypt/index.js.map +1 -0
  53. package/dist/gs/hash/fnv/index.js +13 -5
  54. package/dist/gs/hash/fnv/index.js.map +1 -1
  55. package/dist/gs/io/fs/glob.d.ts +3 -3
  56. package/dist/gs/io/fs/glob.js +8 -8
  57. package/dist/gs/io/fs/glob.js.map +1 -1
  58. package/dist/gs/io/fs/readdir.d.ts +2 -2
  59. package/dist/gs/io/fs/readdir.js +13 -74
  60. package/dist/gs/io/fs/readdir.js.map +1 -1
  61. package/dist/gs/io/fs/sub.js +4 -4
  62. package/dist/gs/io/fs/sub.js.map +1 -1
  63. package/dist/gs/io/fs/walk.js +1 -1
  64. package/dist/gs/io/fs/walk.js.map +1 -1
  65. package/dist/gs/io/io.js +18 -2
  66. package/dist/gs/io/io.js.map +1 -1
  67. package/dist/gs/maps/iter.js.map +1 -1
  68. package/dist/gs/maps/maps.js.map +1 -1
  69. package/dist/gs/mime/index.js +5 -2
  70. package/dist/gs/mime/index.js.map +1 -1
  71. package/dist/gs/net/http/httptest/index.js +6 -3
  72. package/dist/gs/net/http/httptest/index.js.map +1 -1
  73. package/dist/gs/net/http/index.d.ts +16 -4
  74. package/dist/gs/net/http/index.js +236 -40
  75. package/dist/gs/net/http/index.js.map +1 -1
  76. package/dist/gs/net/http/pprof/index.js.map +1 -1
  77. package/dist/gs/reflect/iter.js +1 -1
  78. package/dist/gs/reflect/iter.js.map +1 -1
  79. package/dist/gs/reflect/type.d.ts +2 -0
  80. package/dist/gs/reflect/type.js +53 -21
  81. package/dist/gs/reflect/type.js.map +1 -1
  82. package/dist/gs/runtime/debug/index.js +2 -1
  83. package/dist/gs/runtime/debug/index.js.map +1 -1
  84. package/dist/gs/runtime/pprof/index.js.map +1 -1
  85. package/dist/gs/runtime/runtime.js +2 -2
  86. package/dist/gs/runtime/runtime.js.map +1 -1
  87. package/dist/gs/runtime/trace/index.js.map +1 -1
  88. package/dist/gs/slices/slices.d.ts +1 -1
  89. package/dist/gs/slices/slices.js +37 -4
  90. package/dist/gs/slices/slices.js.map +1 -1
  91. package/go.mod +2 -2
  92. package/go.sum +2 -0
  93. package/gs/builtin/builtin.ts +11 -14
  94. package/gs/builtin/defer.ts +2 -2
  95. package/gs/builtin/hostio.test.ts +8 -3
  96. package/gs/builtin/hostio.ts +5 -7
  97. package/gs/builtin/map.ts +4 -1
  98. package/gs/builtin/slice.test.ts +14 -0
  99. package/gs/builtin/slice.ts +64 -0
  100. package/gs/builtin/type.ts +72 -0
  101. package/gs/bytes/bytes.test.ts +14 -13
  102. package/gs/compress/zlib/index.test.ts +19 -5
  103. package/gs/compress/zlib/index.ts +16 -7
  104. package/gs/context/context.test.ts +3 -1
  105. package/gs/crypto/aes/index.test.ts +120 -0
  106. package/gs/crypto/aes/index.ts +76 -0
  107. package/gs/crypto/cipher/index.ts +345 -0
  108. package/gs/crypto/cipher/meta.json +6 -0
  109. package/gs/crypto/ecdh/index.test.ts +6 -2
  110. package/gs/crypto/ecdh/index.ts +49 -12
  111. package/gs/crypto/ed25519/index.ts +20 -7
  112. package/gs/crypto/rand/index.ts +6 -3
  113. package/gs/embed/index.test.ts +3 -3
  114. package/gs/embed/index.ts +9 -3
  115. package/gs/fmt/fmt.test.ts +29 -4
  116. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +126 -0
  117. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +46 -0
  118. package/gs/github.com/mr-tron/base58/base58/index.ts +9 -3
  119. package/gs/github.com/zeebo/blake3/internal/consts/index.test.ts +2 -8
  120. package/gs/golang.org/x/crypto/chacha20poly1305/index.test.ts +91 -0
  121. package/gs/golang.org/x/crypto/chacha20poly1305/index.ts +245 -0
  122. package/gs/golang.org/x/crypto/scrypt/index.test.ts +81 -0
  123. package/gs/golang.org/x/crypto/scrypt/index.ts +54 -0
  124. package/gs/golang.org/x/crypto/scrypt/meta.json +5 -0
  125. package/gs/hash/fnv/index.test.ts +1 -8
  126. package/gs/hash/fnv/index.ts +27 -10
  127. package/gs/io/fs/glob.ts +13 -10
  128. package/gs/io/fs/meta.json +2 -0
  129. package/gs/io/fs/readdir.test.ts +63 -2
  130. package/gs/io/fs/readdir.ts +33 -30
  131. package/gs/io/fs/sub.ts +4 -4
  132. package/gs/io/fs/walk.ts +1 -1
  133. package/gs/io/io.test.ts +56 -1
  134. package/gs/io/io.ts +19 -2
  135. package/gs/maps/iter.ts +9 -9
  136. package/gs/maps/maps.ts +4 -4
  137. package/gs/math/bits/index.test.ts +10 -1
  138. package/gs/mime/index.test.ts +33 -15
  139. package/gs/mime/index.ts +9 -2
  140. package/gs/net/http/httptest/index.test.ts +17 -3
  141. package/gs/net/http/httptest/index.ts +8 -3
  142. package/gs/net/http/index.test.ts +645 -123
  143. package/gs/net/http/index.ts +548 -113
  144. package/gs/net/http/pprof/index.ts +24 -6
  145. package/gs/os/file_unix_js.test.ts +22 -0
  146. package/gs/reflect/iter.ts +4 -2
  147. package/gs/reflect/map.test.ts +56 -1
  148. package/gs/reflect/type.ts +76 -37
  149. package/gs/runtime/debug/index.test.ts +32 -4
  150. package/gs/runtime/debug/index.ts +5 -2
  151. package/gs/runtime/pprof/index.test.ts +7 -1
  152. package/gs/runtime/pprof/index.ts +5 -1
  153. package/gs/runtime/runtime.test.ts +7 -0
  154. package/gs/runtime/runtime.ts +2 -4
  155. package/gs/runtime/trace/index.test.ts +9 -1
  156. package/gs/runtime/trace/index.ts +5 -1
  157. package/gs/slices/meta.json +3 -0
  158. package/gs/slices/slices.test.ts +59 -21
  159. package/gs/slices/slices.ts +61 -20
  160. package/gs/strconv/complex.test.ts +17 -3
  161. package/gs/sync/atomic/doc_64.test.ts +2 -9
  162. package/gs/sync/sync.test.ts +18 -8
  163. package/gs/syscall/js/index.test.ts +9 -4
  164. package/package.json +13 -5
@@ -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
@@ -194,7 +202,7 @@ func protobufTypeScriptBindingExportedConsts(tsPath string) map[string]bool {
194
202
  return nil
195
203
  }
196
204
  exports := make(map[string]bool)
197
- for _, line := range strings.Split(string(data), "\n") {
205
+ for line := range strings.SplitSeq(string(data), "\n") {
198
206
  line = strings.TrimSpace(line)
199
207
  if !strings.HasPrefix(line, "export const ") {
200
208
  continue
@@ -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 {
@@ -80,6 +80,115 @@ func NewFoo() Foo {
80
80
  }
81
81
  }
82
82
 
83
+ func TestProtobufTypeScriptBindingRewritesGeneratedMethodsToBoundHelpers(t *testing.T) {
84
+ dir := t.TempDir()
85
+ writeTestFile(t, dir, "go.mod", `module example.test/protobufbindingmethods
86
+
87
+ go 1.25
88
+
89
+ require github.com/aperturerobotics/protobuf-go-lite v0.0.0
90
+
91
+ replace github.com/aperturerobotics/protobuf-go-lite => ./protobuf-go-lite
92
+ `)
93
+ writeTestFile(t, dir, "protobuf-go-lite/go.mod", `module github.com/aperturerobotics/protobuf-go-lite
94
+
95
+ go 1.25
96
+ `)
97
+ writeTestFile(t, dir, "protobuf-go-lite/protobuf-go-lite.go", `package protobuf_go_lite
98
+
99
+ type CloneMessage interface {
100
+ CloneMessageVT() CloneMessage
101
+ }
102
+ `)
103
+ writeTestFile(t, dir, "foo.pb.go", `package protobufbindingmethods
104
+
105
+ import protobuf_go_lite "github.com/aperturerobotics/protobuf-go-lite"
106
+
107
+ type Foo struct {
108
+ Name string
109
+ }
110
+
111
+ func (x *Foo) CloneMessageVT() protobuf_go_lite.CloneMessage {
112
+ println("inline CloneMessageVT marker")
113
+ return x.CloneVT()
114
+ }
115
+
116
+ func (x *Foo) CloneVT() *Foo {
117
+ println("inline CloneVT marker")
118
+ return &Foo{Name: x.Name}
119
+ }
120
+
121
+ func (x *Foo) EqualVT(other *Foo) bool {
122
+ println("inline EqualVT marker")
123
+ return other != nil && x.Name == other.Name
124
+ }
125
+
126
+ func (x *Foo) MarshalVT() ([]byte, error) {
127
+ println("inline MarshalVT marker")
128
+ return []byte(x.Name), nil
129
+ }
130
+
131
+ func (x *Foo) MarshalToSizedBufferVT(data []byte) (int, error) {
132
+ println("inline MarshalToSizedBufferVT marker")
133
+ return copy(data, x.Name), nil
134
+ }
135
+
136
+ func (x *Foo) SizeVT() int {
137
+ println("inline SizeVT marker")
138
+ return len(x.Name)
139
+ }
140
+
141
+ func (x *Foo) UnmarshalVT(data []byte) error {
142
+ println("inline UnmarshalVT marker")
143
+ x.Name = string(data)
144
+ return nil
145
+ }
146
+
147
+ func (x *Foo) Reset() {
148
+ println("inline Reset marker")
149
+ *x = Foo{}
150
+ }
151
+ `)
152
+ writeTestFile(t, dir, "foo.pb.ts", `export interface Foo {
153
+ name?: string
154
+ }
155
+ export const Foo = {} as any
156
+ `)
157
+
158
+ out := filepath.Join(dir, "out")
159
+ comp, err := NewCompiler(&Config{
160
+ Dir: dir,
161
+ OutputPath: out,
162
+ ProtobufTypeScriptBinding: true,
163
+ }, nil, nil)
164
+ if err != nil {
165
+ t.Fatal(err)
166
+ }
167
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
168
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
169
+ }
170
+
171
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "protobufbindingmethods", "foo.pb.ts"))
172
+ wantSnippets := []string{
173
+ `$.interfaceValue<protobuf_go_lite.CloneMessage | null>(protobuf_go_lite.CloneBoundMessage(Foo, this) as any, "*protobufbindingmethods.Foo")`,
174
+ `return protobuf_go_lite.CloneBoundMessage(Foo, this) as any`,
175
+ `protobuf_go_lite.EqualBoundMessage(Foo, this, other)`,
176
+ `protobuf_go_lite.MarshalBoundMessageVT(Foo, this)`,
177
+ `protobuf_go_lite.MarshalBoundMessageToSizedBufferVT(Foo, this, data)`,
178
+ `protobuf_go_lite.SizeBoundMessageVT(Foo, this)`,
179
+ `protobuf_go_lite.UnmarshalBoundMessageVT(Foo, this, data)`,
180
+ `$.assignStruct($.pointerValue<Foo>(this), $.markAsStructValue(new Foo()))`,
181
+ }
182
+ for _, snippet := range wantSnippets {
183
+ if !strings.Contains(binding, snippet) {
184
+ t.Fatalf("binding file should rewrite generated methods through %q, got:\n%s", snippet, binding)
185
+ }
186
+ }
187
+ if strings.Contains(binding, "inline ") {
188
+ t.Fatalf("binding file should not preserve inline generated method bodies, got:\n%s", binding)
189
+ }
190
+ }
191
+
83
192
  func TestProtobufTypeScriptBindingEmitsMetadataForPreservedOneofFiles(t *testing.T) {
84
193
  dir := t.TempDir()
85
194
  writeTestFile(t, dir, "go.mod", "module example.test/oneofpb\n\ngo 1.25\n")
@@ -95,7 +204,11 @@ func TestProtobufTypeScriptBindingEmitsMetadataForPreservedOneofFiles(t *testing
95
204
  "type Wrapper_StringValue struct {\n"+
96
205
  "\tStringValue string `protobuf:\"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof\"`\n"+
97
206
  "}\n\n"+
98
- "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")
99
212
  writeTestFile(t, dir, "foo.pb.ts", `export interface Inner {
100
213
  name?: string
101
214
  }
@@ -122,7 +235,8 @@ export const Wrapper = {} as any
122
235
  binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "oneofpb", "foo.pb.ts"))
123
236
  if !strings.Contains(binding, `import * as __protobuf_ts`) ||
124
237
  !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptMessage = __protobuf_ts.Wrapper;`) ||
125
- !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}};`) {
126
240
  t.Fatalf("oneof-preserved protobuf file should still expose TypeScript metadata, got:\n%s", binding)
127
241
  }
128
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