goscript 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/cmd/goscript-wasm/main.go +38 -6
  2. package/compiler/diagnostic.go +104 -12
  3. package/compiler/diagnostic_test.go +106 -0
  4. package/compiler/gotest/runner.go +99 -17
  5. package/compiler/gotest/runner_test.go +65 -0
  6. package/compiler/index.test.ts +23 -0
  7. package/compiler/lowered-program.go +9 -7
  8. package/compiler/lowering.go +361 -72
  9. package/compiler/lowering_bench_test.go +1 -0
  10. package/compiler/lowering_internal_test.go +18 -0
  11. package/compiler/protobuf-ts-binding.go +65 -12
  12. package/compiler/protobuf-ts-binding_test.go +339 -0
  13. package/compiler/runtime-contract.go +4 -0
  14. package/compiler/runtime-contract_test.go +2 -0
  15. package/compiler/service.go +1 -0
  16. package/compiler/skeleton_test.go +60 -3
  17. package/compiler/wasm/compile_test.go +37 -4
  18. package/compiler/wasm-api.go +57 -7
  19. package/dist/gs/builtin/hostio.js +6 -1
  20. package/dist/gs/builtin/hostio.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +11 -1
  22. package/dist/gs/builtin/slice.js +158 -2
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/crypto/aes/index.d.ts +15 -0
  25. package/dist/gs/crypto/aes/index.js +57 -0
  26. package/dist/gs/crypto/aes/index.js.map +1 -0
  27. package/dist/gs/crypto/cipher/index.d.ts +41 -0
  28. package/dist/gs/crypto/cipher/index.js +255 -0
  29. package/dist/gs/crypto/cipher/index.js.map +1 -0
  30. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  31. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
  32. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  33. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
  34. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
  35. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  36. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.d.ts +31 -0
  37. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js +117 -0
  38. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js.map +1 -0
  39. package/dist/gs/internal/byteorder/index.js +2 -2
  40. package/dist/gs/internal/byteorder/index.js.map +1 -1
  41. package/dist/gs/io/io.js +18 -2
  42. package/dist/gs/io/io.js.map +1 -1
  43. package/dist/gs/reflect/type.js +57 -0
  44. package/dist/gs/reflect/type.js.map +1 -1
  45. package/dist/gs/runtime/debug/index.js +2 -1
  46. package/dist/gs/runtime/debug/index.js.map +1 -1
  47. package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
  48. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  49. package/go.mod +2 -2
  50. package/go.sum +2 -0
  51. package/gs/builtin/hostio.test.ts +22 -1
  52. package/gs/builtin/hostio.ts +6 -1
  53. package/gs/builtin/runtime-contract.test.ts +28 -0
  54. package/gs/builtin/slice.ts +225 -20
  55. package/gs/crypto/aes/index.test.ts +120 -0
  56. package/gs/crypto/aes/index.ts +76 -0
  57. package/gs/crypto/cipher/index.ts +345 -0
  58. package/gs/crypto/cipher/meta.json +6 -0
  59. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
  60. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
  61. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
  62. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
  63. package/gs/golang.org/x/crypto/chacha20poly1305/index.test.ts +91 -0
  64. package/gs/golang.org/x/crypto/chacha20poly1305/index.ts +245 -0
  65. package/gs/internal/byteorder/index.test.ts +2 -2
  66. package/gs/internal/byteorder/index.ts +2 -2
  67. package/gs/io/io.test.ts +56 -1
  68. package/gs/io/io.ts +19 -2
  69. package/gs/reflect/type.ts +64 -0
  70. package/gs/reflect/typefor.test.ts +21 -1
  71. package/gs/runtime/debug/index.test.ts +32 -4
  72. package/gs/runtime/debug/index.ts +5 -2
  73. package/gs/sync/atomic/doc_64.gs.ts +6 -7
  74. package/gs/sync/atomic/doc_64.test.ts +43 -0
  75. package/package.json +10 -3
@@ -88,6 +88,7 @@ func BenchmarkLoweringFile(b *testing.B) {
88
88
  fixture.lazyPackageVarsByPkg,
89
89
  make(runtimeMethodSetCache),
90
90
  false,
91
+ "",
91
92
  ); diagnosticsHaveErrors(diagnostics) {
92
93
  b.Fatal(diagnostics)
93
94
  }
@@ -0,0 +1,18 @@
1
+ package compiler
2
+
3
+ import "testing"
4
+
5
+ func TestIntegerBitHelpersHandleNilTypes(t *testing.T) {
6
+ if bits, ok := unsignedIntegerBits(nil); ok || bits != 0 {
7
+ t.Fatalf("unsignedIntegerBits(nil) = %d, %v; want 0, false", bits, ok)
8
+ }
9
+ if bits, ok := signedIntegerBits(nil); ok || bits != 0 {
10
+ t.Fatalf("signedIntegerBits(nil) = %d, %v; want 0, false", bits, ok)
11
+ }
12
+ if bits, ok := integerBits(nil); ok || bits != 0 {
13
+ t.Fatalf("integerBits(nil) = %d, %v; want 0, false", bits, ok)
14
+ }
15
+ if isRuntimeWideIntegerType(nil) {
16
+ t.Fatalf("isRuntimeWideIntegerType(nil) = true; want false")
17
+ }
18
+ }
@@ -66,7 +66,7 @@ func protobufTypeScriptBindings(semPkg *semanticPackage, options LoweringOptions
66
66
  sourcePath: sourcePath,
67
67
  outputName: strings.TrimSuffix(filepath.Base(sourcePath), ".go") + ".ts",
68
68
  importSource: importSource,
69
- messageNames: protobufTypeScriptBindingMessageNames(syntax),
69
+ messageNames: protobufTypeScriptBindingMessageNames(syntax, tsPath),
70
70
  hasOneof: protobufTypeScriptBindingHasOneof(syntax),
71
71
  }
72
72
  }
@@ -158,11 +158,12 @@ func protobufTypeScriptBindingHasOneof(file *ast.File) bool {
158
158
  return false
159
159
  }
160
160
 
161
- func protobufTypeScriptBindingMessageNames(file *ast.File) map[string]string {
161
+ func protobufTypeScriptBindingMessageNames(file *ast.File, tsPath string) map[string]string {
162
162
  names := make(map[string]string)
163
163
  if file == nil {
164
164
  return names
165
165
  }
166
+ exportedTSMessages := protobufTypeScriptBindingExportedConsts(tsPath)
166
167
  for _, decl := range file.Decls {
167
168
  genDecl, ok := decl.(*ast.GenDecl)
168
169
  if !ok {
@@ -177,12 +178,46 @@ func protobufTypeScriptBindingMessageNames(file *ast.File) map[string]string {
177
178
  continue
178
179
  }
179
180
  name := typeSpec.Name.Name
180
- names[name] = protobufTypeScriptBindingSafeIdentifier(name)
181
+ safeName := protobufTypeScriptBindingSafeIdentifier(name)
182
+ if len(exportedTSMessages) != 0 && !exportedTSMessages[safeName] {
183
+ continue
184
+ }
185
+ names[name] = safeName
181
186
  }
182
187
  }
183
188
  return names
184
189
  }
185
190
 
191
+ func protobufTypeScriptBindingExportedConsts(tsPath string) map[string]bool {
192
+ data, err := os.ReadFile(tsPath)
193
+ if err != nil {
194
+ return nil
195
+ }
196
+ exports := make(map[string]bool)
197
+ for line := range strings.SplitSeq(string(data), "\n") {
198
+ line = strings.TrimSpace(line)
199
+ if !strings.HasPrefix(line, "export const ") {
200
+ continue
201
+ }
202
+ rest := strings.TrimPrefix(line, "export const ")
203
+ end := 0
204
+ for end < len(rest) && protobufTypeScriptBindingIdentifierByte(rest[end]) {
205
+ end++
206
+ }
207
+ if end != 0 {
208
+ exports[rest[:end]] = true
209
+ }
210
+ }
211
+ return exports
212
+ }
213
+
214
+ func protobufTypeScriptBindingIdentifierByte(ch byte) bool {
215
+ return ch == '_' || ch == '$' ||
216
+ ch >= '0' && ch <= '9' ||
217
+ ch >= 'A' && ch <= 'Z' ||
218
+ ch >= 'a' && ch <= 'z'
219
+ }
220
+
186
221
  func protobufTypeScriptBindingSafeIdentifier(name string) string {
187
222
  switch name {
188
223
  case "break",
@@ -250,9 +285,6 @@ func rewriteProtobufTypeScriptBindingFile(file *loweredFile, binding protobufTyp
250
285
  return
251
286
  }
252
287
  file.outputName = binding.outputName
253
- if binding.hasOneof {
254
- return
255
- }
256
288
  const importAlias = "__protobuf_ts"
257
289
  file.imports = append(file.imports, loweredImport{
258
290
  alias: importAlias,
@@ -267,8 +299,14 @@ func rewriteProtobufTypeScriptBindingFile(file *loweredFile, binding protobufTyp
267
299
  if protobufTypeScriptBindingSyntheticMapEntry(decl.structType.name) {
268
300
  continue
269
301
  }
270
- rewriteProtobufTypeScriptBindingStruct(decl.structType)
271
- setup := protobufTypeScriptBindingStructSetupDecl(decl.structType, importAlias, binding.messageNames[decl.structType.name])
302
+ if !binding.hasOneof {
303
+ rewriteProtobufTypeScriptBindingStruct(decl.structType, binding.sourcePath)
304
+ }
305
+ messageName, ok := binding.messageNames[decl.structType.name]
306
+ if !ok {
307
+ continue
308
+ }
309
+ setup := protobufTypeScriptBindingStructSetupDecl(decl.structType, importAlias, messageName)
272
310
  if setup.code != "" {
273
311
  setupDecls = append(setupDecls, setup)
274
312
  }
@@ -322,12 +360,18 @@ func protobufSRPCHasGoScriptReplacement(sourcePath string) bool {
322
360
  return err == nil
323
361
  }
324
362
 
325
- func rewriteProtobufTypeScriptBindingStruct(structType *loweredStruct) {
363
+ func rewriteProtobufTypeScriptBindingStruct(structType *loweredStruct, bindingSourcePath string) {
326
364
  if structType == nil {
327
365
  return
328
366
  }
329
367
  for idx := range structType.methods {
330
368
  method := &structType.methods[idx]
369
+ if method.sourcePath != bindingSourcePath {
370
+ continue
371
+ }
372
+ if structType.protobufPreserveJSON && protobufTypeScriptBindingJSONMethodName(method.name) {
373
+ continue
374
+ }
331
375
  body := protobufTypeScriptBindingMethodBody(structType, method)
332
376
  if body == "" {
333
377
  continue
@@ -383,6 +427,15 @@ func protobufTypeScriptBindingMethodBody(structType *loweredStruct, method *lowe
383
427
  }
384
428
  }
385
429
 
430
+ func protobufTypeScriptBindingJSONMethodName(name string) bool {
431
+ switch name {
432
+ case "MarshalJSON", "MarshalProtoJSON", "UnmarshalJSON", "UnmarshalProtoJSON":
433
+ return true
434
+ default:
435
+ return false
436
+ }
437
+ }
438
+
386
439
  func protobufTypeScriptBindingReplacesMethodName(name string) bool {
387
440
  switch name {
388
441
  case "CloneMessageVT",
@@ -448,11 +501,11 @@ func protobufTypeScriptBindingFieldLocalName(field loweredStructField) string {
448
501
  }
449
502
 
450
503
  func protobufTypeScriptBindingTagValue(tag, key string) string {
451
- idx := strings.Index(tag, key)
452
- if idx < 0 {
504
+ _, after, ok := strings.Cut(tag, key)
505
+ if !ok {
453
506
  return ""
454
507
  }
455
- rest := tag[idx+len(key):]
508
+ rest := after
456
509
  end := len(rest)
457
510
  for idx, ch := range rest {
458
511
  if ch == ',' || ch == '"' || ch == '`' || ch == ' ' {
@@ -80,6 +80,345 @@ 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
+
192
+ func TestProtobufTypeScriptBindingEmitsMetadataForPreservedOneofFiles(t *testing.T) {
193
+ dir := t.TempDir()
194
+ writeTestFile(t, dir, "go.mod", "module example.test/oneofpb\n\ngo 1.25\n")
195
+ writeTestFile(t, dir, "foo.pb.go", "package oneofpb\n\n"+
196
+ "type Inner struct {\n"+
197
+ "\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n"+
198
+ "}\n\n"+
199
+ "type Wrapper struct {\n"+
200
+ "\tInner *Inner `protobuf:\"bytes,1,opt,name=inner,proto3\" json:\"inner,omitempty\"`\n"+
201
+ "\tChoice isWrapper_Choice `protobuf_oneof:\"choice\"`\n"+
202
+ "}\n\n"+
203
+ "type isWrapper_Choice interface { isWrapper_Choice() }\n\n"+
204
+ "type Wrapper_StringValue struct {\n"+
205
+ "\tStringValue string `protobuf:\"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof\"`\n"+
206
+ "}\n\n"+
207
+ "func (*Wrapper_StringValue) isWrapper_Choice() {}\n")
208
+ writeTestFile(t, dir, "foo.pb.ts", `export interface Inner {
209
+ name?: string
210
+ }
211
+ export const Inner = {} as any
212
+ export interface Wrapper {
213
+ inner?: Inner
214
+ }
215
+ export const Wrapper = {} as any
216
+ `)
217
+
218
+ out := filepath.Join(dir, "out")
219
+ comp, err := NewCompiler(&Config{
220
+ Dir: dir,
221
+ OutputPath: out,
222
+ ProtobufTypeScriptBinding: true,
223
+ }, nil, nil)
224
+ if err != nil {
225
+ t.Fatal(err)
226
+ }
227
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
228
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
229
+ }
230
+
231
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "oneofpb", "foo.pb.ts"))
232
+ if !strings.Contains(binding, `import * as __protobuf_ts`) ||
233
+ !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptMessage = __protobuf_ts.Wrapper;`) ||
234
+ !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptFields = {"inner": Inner};`) {
235
+ t.Fatalf("oneof-preserved protobuf file should still expose TypeScript metadata, got:\n%s", binding)
236
+ }
237
+ if strings.Contains(binding, `__protobuf_ts.Wrapper_StringValue`) {
238
+ t.Fatalf("oneof wrapper structs should not reference missing TypeScript exports, got:\n%s", binding)
239
+ }
240
+ }
241
+
242
+ func TestProtobufTypeScriptBindingPreservesCustomJSONMethods(t *testing.T) {
243
+ dir := t.TempDir()
244
+ writeTestFile(t, dir, "go.mod", "module example.test/custompbjson\n\ngo 1.25\n")
245
+ writeTestFile(t, dir, "foo.pb.go", `package custompbjson
246
+
247
+ type Foo struct {
248
+ Config []byte
249
+ }
250
+ `)
251
+ writeTestFile(t, dir, "foo.pb.ts", `export interface Foo {
252
+ config?: Uint8Array
253
+ }
254
+ export const Foo = {} as any
255
+ `)
256
+ writeTestFile(t, dir, "foo-json.go", `package custompbjson
257
+
258
+ func (x *Foo) UnmarshalJSON(b []byte) error {
259
+ x.Config = b
260
+ return nil
261
+ }
262
+ `)
263
+
264
+ out := filepath.Join(dir, "out")
265
+ comp, err := NewCompiler(&Config{
266
+ Dir: dir,
267
+ OutputPath: out,
268
+ ProtobufTypeScriptBinding: true,
269
+ }, nil, nil)
270
+ if err != nil {
271
+ t.Fatal(err)
272
+ }
273
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
274
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
275
+ }
276
+
277
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "custompbjson", "foo.pb.ts"))
278
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Foo`) {
279
+ t.Fatalf("custom JSON method should not be replaced by generic protobuf helper, got:\n%s", binding)
280
+ }
281
+ if !strings.Contains(binding, `.Config = b`) {
282
+ t.Fatalf("custom JSON method body should be preserved, got:\n%s", binding)
283
+ }
284
+ }
285
+
286
+ func TestProtobufTypeScriptBindingPreservesJSONGraphWithCustomNestedMessage(t *testing.T) {
287
+ dir := t.TempDir()
288
+ writeTestFile(t, dir, "go.mod", "module example.test/nestedcustompbjson\n\ngo 1.25\n")
289
+ writeTestFile(t, dir, "foo.pb.go", `package nestedcustompbjson
290
+
291
+ type Foo struct {
292
+ Items []*Item
293
+ }
294
+
295
+ type Item struct {
296
+ Config []byte
297
+ }
298
+
299
+ func generatedFooJSONMarker(x *Foo) error {
300
+ return nil
301
+ }
302
+
303
+ func (x *Foo) UnmarshalJSON(b []byte) error {
304
+ return generatedFooJSONMarker(x)
305
+ }
306
+
307
+ func (x *Foo) UnmarshalProtoJSON(s any) {
308
+ }
309
+ `)
310
+ writeTestFile(t, dir, "foo.pb.ts", `export interface Foo {
311
+ items?: Item[]
312
+ }
313
+ export const Foo = {} as any
314
+ export interface Item {
315
+ config?: Uint8Array
316
+ }
317
+ export const Item = {} as any
318
+ `)
319
+ writeTestFile(t, dir, "item-json.go", `package nestedcustompbjson
320
+
321
+ func (x *Item) UnmarshalJSON(b []byte) error {
322
+ x.Config = b
323
+ return nil
324
+ }
325
+ `)
326
+
327
+ out := filepath.Join(dir, "out")
328
+ comp, err := NewCompiler(&Config{
329
+ Dir: dir,
330
+ OutputPath: out,
331
+ ProtobufTypeScriptBinding: true,
332
+ }, nil, nil)
333
+ if err != nil {
334
+ t.Fatal(err)
335
+ }
336
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
337
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
338
+ }
339
+
340
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "nestedcustompbjson", "foo.pb.ts"))
341
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Foo`) {
342
+ t.Fatalf("outer message with nested custom JSON should keep generated JSON body, got:\n%s", binding)
343
+ }
344
+ if !strings.Contains(binding, `generatedFooJSONMarker`) {
345
+ t.Fatalf("outer generated JSON body should be preserved, got:\n%s", binding)
346
+ }
347
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Item`) {
348
+ t.Fatalf("nested custom JSON method should not be replaced, got:\n%s", binding)
349
+ }
350
+ if !strings.Contains(binding, `.Config = b`) {
351
+ t.Fatalf("nested custom JSON method body should be preserved, got:\n%s", binding)
352
+ }
353
+ }
354
+
355
+ func TestProtobufTypeScriptBindingPreservesJSONGraphWithImportedCustomNestedMessage(t *testing.T) {
356
+ dir := t.TempDir()
357
+ writeTestFile(t, dir, "go.mod", "module example.test/importedcustompbjson\n\ngo 1.25\n")
358
+ writeTestFile(t, dir, "inner/inner.pb.go", `package inner
359
+
360
+ type Inner struct {
361
+ Config []byte
362
+ }
363
+ `)
364
+ writeTestFile(t, dir, "inner/inner.pb.ts", `export interface Inner {
365
+ config?: Uint8Array
366
+ }
367
+ export const Inner = {} as any
368
+ `)
369
+ writeTestFile(t, dir, "inner/inner-json.go", `package inner
370
+
371
+ func (x *Inner) UnmarshalJSON(b []byte) error {
372
+ x.Config = b
373
+ return nil
374
+ }
375
+ `)
376
+ writeTestFile(t, dir, "outer.pb.go", `package importedcustompbjson
377
+
378
+ import "example.test/importedcustompbjson/inner"
379
+
380
+ type Outer struct {
381
+ Inner *inner.Inner
382
+ }
383
+
384
+ func generatedOuterJSONMarker(x *Outer) error {
385
+ return nil
386
+ }
387
+
388
+ func (x *Outer) UnmarshalJSON(b []byte) error {
389
+ return generatedOuterJSONMarker(x)
390
+ }
391
+ `)
392
+ writeTestFile(t, dir, "outer.pb.ts", `import type { Inner } from './inner/inner.pb.js'
393
+
394
+ export interface Outer {
395
+ inner?: Inner
396
+ }
397
+ export const Outer = {} as any
398
+ `)
399
+
400
+ out := filepath.Join(dir, "out")
401
+ comp, err := NewCompiler(&Config{
402
+ Dir: dir,
403
+ OutputPath: out,
404
+ ProtobufTypeScriptBinding: true,
405
+ }, nil, nil)
406
+ if err != nil {
407
+ t.Fatal(err)
408
+ }
409
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
410
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
411
+ }
412
+
413
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "importedcustompbjson", "outer.pb.ts"))
414
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Outer`) {
415
+ t.Fatalf("outer message with imported nested custom JSON should keep generated JSON body, got:\n%s", binding)
416
+ }
417
+ if !strings.Contains(binding, `generatedOuterJSONMarker`) {
418
+ t.Fatalf("outer generated JSON body should be preserved, got:\n%s", binding)
419
+ }
420
+ }
421
+
83
422
  func TestProtobufTypeScriptBindingReportsMissingSibling(t *testing.T) {
84
423
  dir := t.TempDir()
85
424
  writeTestFile(t, dir, "go.mod", "module example.test/missingpbts\n\ngo 1.25\n")
@@ -105,6 +105,8 @@ const (
105
105
  RuntimeHelperSliceStringOrBytes RuntimeHelper = "slice.sliceStringOrBytes"
106
106
  RuntimeHelperIndexRef RuntimeHelper = "slice.indexRef"
107
107
  RuntimeHelperIndexAddress RuntimeHelper = "slice.indexAddress"
108
+ RuntimeHelperIndexByteAddress RuntimeHelper = "slice.indexByteAddress"
109
+ RuntimeHelperUnsafePointerRef RuntimeHelper = "slice.unsafePointerRef"
108
110
 
109
111
  RuntimeHelperMakeMap RuntimeHelper = "map.makeMap"
110
112
  RuntimeHelperMapGet RuntimeHelper = "map.mapGet"
@@ -333,6 +335,8 @@ func runtimeHelperContracts() []RuntimeHelperContract {
333
335
  runtimeHelper(RuntimeHelperSliceStringOrBytes, "sliceStringOrBytes", RuntimeHelperCategorySlice),
334
336
  runtimeHelper(RuntimeHelperIndexRef, "indexRef", RuntimeHelperCategorySlice),
335
337
  runtimeHelper(RuntimeHelperIndexAddress, "indexAddress", RuntimeHelperCategorySlice),
338
+ runtimeHelper(RuntimeHelperIndexByteAddress, "indexByteAddress", RuntimeHelperCategorySlice),
339
+ runtimeHelper(RuntimeHelperUnsafePointerRef, "unsafePointerRef", RuntimeHelperCategorySlice),
336
340
  runtimeHelper(RuntimeHelperMakeMap, "makeMap", RuntimeHelperCategoryMap),
337
341
  runtimeHelper(RuntimeHelperMapGet, "mapGet", RuntimeHelperCategoryMap),
338
342
  runtimeHelper(RuntimeHelperMapSet, "mapSet", RuntimeHelperCategoryMap),
@@ -25,6 +25,8 @@ func TestRuntimeContractOwnsBuiltinImportAndHelpers(t *testing.T) {
25
25
  RuntimeHelperMakeSlice: RuntimeHelperCategorySlice,
26
26
  RuntimeHelperAppend: RuntimeHelperCategorySlice,
27
27
  RuntimeHelperIndexAddress: RuntimeHelperCategorySlice,
28
+ RuntimeHelperIndexByteAddress: RuntimeHelperCategorySlice,
29
+ RuntimeHelperUnsafePointerRef: RuntimeHelperCategorySlice,
28
30
  RuntimeHelperMakeMap: RuntimeHelperCategoryMap,
29
31
  RuntimeHelperMapGet: RuntimeHelperCategoryMap,
30
32
  RuntimeHelperNewError: RuntimeHelperCategoryError,
@@ -127,6 +127,7 @@ func (s *CompileService) Compile(ctx context.Context, req *CompileRequest) (*Com
127
127
 
128
128
  loweredProgram, loweringDiagnostics := s.loweringOwner.Build(ctx, semanticModel, LoweringOptions{
129
129
  SourceRoot: protobufTypeScriptBindingRoot(req.Dir),
130
+ DisplayRoot: req.Dir,
130
131
  OutputPath: req.OutputPath,
131
132
  ProtobufTypeScriptBinding: req.ProtobufTypeScriptBinding,
132
133
  })
@@ -1496,6 +1496,56 @@ func TestCompilePackagesEmitsIndexAddressRefs(t *testing.T) {
1496
1496
  }
1497
1497
  }
1498
1498
 
1499
+ func TestCompilePackagesLowersUnsafeBytePointerArithmetic(t *testing.T) {
1500
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1501
+ "go.mod": "module example.test/unsafeaddr\n\ngo 1.25.3\n",
1502
+ "main.go": strings.Join([]string{
1503
+ "package main",
1504
+ "import \"unsafe\"",
1505
+ "func Set(bits []uint64, idx uint64, mask []uint8) uint8 {",
1506
+ " ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&bits[idx>>6])) + uintptr((idx%64)>>3))",
1507
+ " *(*uint8)(ptr) |= mask[idx%8]",
1508
+ " return *(*uint8)(ptr)",
1509
+ "}",
1510
+ "func CopyBlock(dst []byte, words *[16]uint32) {",
1511
+ " *(*[64]byte)(unsafe.Pointer(&dst[0])) = *(*[64]byte)(unsafe.Pointer(&words[0]))",
1512
+ "}",
1513
+ "",
1514
+ }, "\n"),
1515
+ })
1516
+ outputDir := filepath.Join(t.TempDir(), "output")
1517
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1518
+ if err != nil {
1519
+ t.Fatal(err.Error())
1520
+ }
1521
+
1522
+ _, err = comp.CompilePackages(context.Background(), ".")
1523
+ if err != nil {
1524
+ t.Fatal(err.Error())
1525
+ }
1526
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "unsafeaddr", "main.gs.ts")
1527
+ content, err := os.ReadFile(outputFile)
1528
+ if err != nil {
1529
+ t.Fatal(err.Error())
1530
+ }
1531
+ text := string(content)
1532
+ if !strings.Contains(text, "$.indexByteAddress(bits!, $.uint64Shr(idx, 6), 8)") {
1533
+ t.Fatalf("missing byte-addressed unsafe pointer root:\n%s", text)
1534
+ }
1535
+ if !strings.Contains(text, "$.unsafePointerRef<number>(ptr).value =") {
1536
+ t.Fatalf("missing unsafe pointer storage ref:\n%s", text)
1537
+ }
1538
+ if !strings.Contains(text, "return $.uint($.unsafePointerRef<number>(ptr).value, 8)") {
1539
+ t.Fatalf("missing unsafe pointer value ref:\n%s", text)
1540
+ }
1541
+ if !strings.Contains(text, "$.arrayPointerFromIndexRef<number>($.indexRef($.pointerValue<number[]>(words), 0), 64, 4, 1)") {
1542
+ t.Fatalf("missing byte-view array pointer conversion:\n%s", text)
1543
+ }
1544
+ if !strings.Contains(text, "($.arrayPointerFromIndexRef<number>($.indexRef(dst!, 0), 64, 1, 1) as unknown as $.VarRef<Uint8Array> | null)!.value =") {
1545
+ t.Fatalf("missing non-null byte-view array pointer storage:\n%s", text)
1546
+ }
1547
+ }
1548
+
1499
1549
  func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
1500
1550
  moduleDir := writePackageGraphFixture(t, map[string]string{
1501
1551
  "go.mod": "module example.test/structs\n\ngo 1.25.3\n",
@@ -2388,6 +2438,7 @@ func TestCompilePackagesEmitsInterfacesMethodValuesTypeSwitchesAndFunctionAssert
2388
2438
  "Read(): string",
2389
2439
  "Close(): string",
2390
2440
  "$.registerInterfaceType(\n\t\"main.ReadCloser\"",
2441
+ "{ name: \"Close\", args: [], returns: [{ name: \"_r0\", type: { kind: $.TypeKind.Basic, name: \"string\" } }] }]\n);",
2391
2442
  "((__receiver) => () => __receiver.Inc())($.pointerValue<Counter>(counter))",
2392
2443
  "$.namedFunction(greet, \"main.Greeter\", ({ kind: $.TypeKind.Function, name: \"main.Greeter\"",
2393
2444
  "params: [{ kind: $.TypeKind.Basic, name: \"string\" }]",
@@ -3388,7 +3439,7 @@ func TestCompilePackagesEmitsAsyncChannelsSelectAndDefer(t *testing.T) {
3388
3439
  for _, want := range []string{
3389
3440
  "Process(v: number): number | globalThis.Promise<number>",
3390
3441
  "public async Process(v: number): globalThis.Promise<number>",
3391
- "let ch = $.makeChannel<number>(1, 0, \"both\")",
3442
+ "let ch: $.Channel<number> | null = $.makeChannel<number>(1, 0, \"both\")",
3392
3443
  "await $.chanSend($.pointerValue<Worker>(w).ch, v)",
3393
3444
  "return await $.chanRecv($.pointerValue<Worker>(w).ch)",
3394
3445
  "await using __defer = new $.AsyncDisposableStack()",
@@ -4353,10 +4404,15 @@ func TestCompilePackagesLowersUnaryBitwiseComplement(t *testing.T) {
4353
4404
  "main.go": strings.Join([]string{
4354
4405
  "package main",
4355
4406
  "var value = 1",
4407
+ "var wide uint64 = 1",
4408
+ "var signed int64 = 1",
4409
+ "func invert(crc uint64) uint64 {",
4410
+ " return ^crc",
4411
+ "}",
4356
4412
  "func main() {",
4357
4413
  " mask := 7",
4358
4414
  " mask &^= 3",
4359
- " println(^value, value &^ 3, mask, 0700)",
4415
+ " println(^value, ^wide, ^signed, invert(wide), value &^ 3, mask, 0700)",
4360
4416
  "}",
4361
4417
  "",
4362
4418
  }, "\n"),
@@ -4377,8 +4433,9 @@ func TestCompilePackagesLowersUnaryBitwiseComplement(t *testing.T) {
4377
4433
  }
4378
4434
  text := string(content)
4379
4435
  for _, want := range []string{
4436
+ "return $.uint($.uint64Xor(crc, -1n), 64)",
4380
4437
  "mask = mask & ~((3))",
4381
- "$.println(~value, value & ~(3), mask, 0o700)",
4438
+ "$.println($.int64Xor(value, -1n), $.uint($.uint64Xor(wide, -1n), 64), $.int($.int64Xor(signed, -1n)), $.uint(invert($.uint(wide, 64)), 64), value & ~(3), mask, 0o700)",
4382
4439
  } {
4383
4440
  if !strings.Contains(text, want) {
4384
4441
  t.Fatalf("missing %q in generated output:\n%s", want, text)