goscript 0.2.0 → 0.2.1

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 (50) 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 +1 -17
  5. package/compiler/gotest/runner_test.go +20 -0
  6. package/compiler/index.test.ts +23 -0
  7. package/compiler/lowered-program.go +9 -7
  8. package/compiler/lowering.go +359 -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 +230 -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 +56 -2
  17. package/compiler/wasm/compile_test.go +37 -4
  18. package/compiler/wasm-api.go +57 -7
  19. package/dist/gs/builtin/hostio.js +5 -0
  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/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  25. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
  26. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  27. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
  28. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
  29. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  30. package/dist/gs/internal/byteorder/index.js +2 -2
  31. package/dist/gs/internal/byteorder/index.js.map +1 -1
  32. package/dist/gs/reflect/type.js +57 -0
  33. package/dist/gs/reflect/type.js.map +1 -1
  34. package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
  35. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  36. package/gs/builtin/hostio.test.ts +16 -0
  37. package/gs/builtin/hostio.ts +7 -0
  38. package/gs/builtin/runtime-contract.test.ts +28 -0
  39. package/gs/builtin/slice.ts +225 -20
  40. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
  41. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
  42. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
  43. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
  44. package/gs/internal/byteorder/index.test.ts +2 -2
  45. package/gs/internal/byteorder/index.ts +2 -2
  46. package/gs/reflect/type.ts +64 -0
  47. package/gs/reflect/typefor.test.ts +21 -1
  48. package/gs/sync/atomic/doc_64.gs.ts +6 -7
  49. package/gs/sync/atomic/doc_64.test.ts +43 -0
  50. package/package.json +1 -1
@@ -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.Split(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,236 @@ func NewFoo() Foo {
80
80
  }
81
81
  }
82
82
 
83
+ func TestProtobufTypeScriptBindingEmitsMetadataForPreservedOneofFiles(t *testing.T) {
84
+ dir := t.TempDir()
85
+ writeTestFile(t, dir, "go.mod", "module example.test/oneofpb\n\ngo 1.25\n")
86
+ writeTestFile(t, dir, "foo.pb.go", "package oneofpb\n\n"+
87
+ "type Inner struct {\n"+
88
+ "\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n"+
89
+ "}\n\n"+
90
+ "type Wrapper struct {\n"+
91
+ "\tInner *Inner `protobuf:\"bytes,1,opt,name=inner,proto3\" json:\"inner,omitempty\"`\n"+
92
+ "\tChoice isWrapper_Choice `protobuf_oneof:\"choice\"`\n"+
93
+ "}\n\n"+
94
+ "type isWrapper_Choice interface { isWrapper_Choice() }\n\n"+
95
+ "type Wrapper_StringValue struct {\n"+
96
+ "\tStringValue string `protobuf:\"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof\"`\n"+
97
+ "}\n\n"+
98
+ "func (*Wrapper_StringValue) isWrapper_Choice() {}\n")
99
+ writeTestFile(t, dir, "foo.pb.ts", `export interface Inner {
100
+ name?: string
101
+ }
102
+ export const Inner = {} as any
103
+ export interface Wrapper {
104
+ inner?: Inner
105
+ }
106
+ export const Wrapper = {} as any
107
+ `)
108
+
109
+ out := filepath.Join(dir, "out")
110
+ comp, err := NewCompiler(&Config{
111
+ Dir: dir,
112
+ OutputPath: out,
113
+ ProtobufTypeScriptBinding: true,
114
+ }, nil, nil)
115
+ if err != nil {
116
+ t.Fatal(err)
117
+ }
118
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
119
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
120
+ }
121
+
122
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "oneofpb", "foo.pb.ts"))
123
+ if !strings.Contains(binding, `import * as __protobuf_ts`) ||
124
+ !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptMessage = __protobuf_ts.Wrapper;`) ||
125
+ !strings.Contains(binding, `(Wrapper as any).__protobufTypeScriptFields = {"inner": Inner};`) {
126
+ t.Fatalf("oneof-preserved protobuf file should still expose TypeScript metadata, got:\n%s", binding)
127
+ }
128
+ if strings.Contains(binding, `__protobuf_ts.Wrapper_StringValue`) {
129
+ t.Fatalf("oneof wrapper structs should not reference missing TypeScript exports, got:\n%s", binding)
130
+ }
131
+ }
132
+
133
+ func TestProtobufTypeScriptBindingPreservesCustomJSONMethods(t *testing.T) {
134
+ dir := t.TempDir()
135
+ writeTestFile(t, dir, "go.mod", "module example.test/custompbjson\n\ngo 1.25\n")
136
+ writeTestFile(t, dir, "foo.pb.go", `package custompbjson
137
+
138
+ type Foo struct {
139
+ Config []byte
140
+ }
141
+ `)
142
+ writeTestFile(t, dir, "foo.pb.ts", `export interface Foo {
143
+ config?: Uint8Array
144
+ }
145
+ export const Foo = {} as any
146
+ `)
147
+ writeTestFile(t, dir, "foo-json.go", `package custompbjson
148
+
149
+ func (x *Foo) UnmarshalJSON(b []byte) error {
150
+ x.Config = b
151
+ return nil
152
+ }
153
+ `)
154
+
155
+ out := filepath.Join(dir, "out")
156
+ comp, err := NewCompiler(&Config{
157
+ Dir: dir,
158
+ OutputPath: out,
159
+ ProtobufTypeScriptBinding: true,
160
+ }, nil, nil)
161
+ if err != nil {
162
+ t.Fatal(err)
163
+ }
164
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
165
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
166
+ }
167
+
168
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "custompbjson", "foo.pb.ts"))
169
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Foo`) {
170
+ t.Fatalf("custom JSON method should not be replaced by generic protobuf helper, got:\n%s", binding)
171
+ }
172
+ if !strings.Contains(binding, `.Config = b`) {
173
+ t.Fatalf("custom JSON method body should be preserved, got:\n%s", binding)
174
+ }
175
+ }
176
+
177
+ func TestProtobufTypeScriptBindingPreservesJSONGraphWithCustomNestedMessage(t *testing.T) {
178
+ dir := t.TempDir()
179
+ writeTestFile(t, dir, "go.mod", "module example.test/nestedcustompbjson\n\ngo 1.25\n")
180
+ writeTestFile(t, dir, "foo.pb.go", `package nestedcustompbjson
181
+
182
+ type Foo struct {
183
+ Items []*Item
184
+ }
185
+
186
+ type Item struct {
187
+ Config []byte
188
+ }
189
+
190
+ func generatedFooJSONMarker(x *Foo) error {
191
+ return nil
192
+ }
193
+
194
+ func (x *Foo) UnmarshalJSON(b []byte) error {
195
+ return generatedFooJSONMarker(x)
196
+ }
197
+
198
+ func (x *Foo) UnmarshalProtoJSON(s any) {
199
+ }
200
+ `)
201
+ writeTestFile(t, dir, "foo.pb.ts", `export interface Foo {
202
+ items?: Item[]
203
+ }
204
+ export const Foo = {} as any
205
+ export interface Item {
206
+ config?: Uint8Array
207
+ }
208
+ export const Item = {} as any
209
+ `)
210
+ writeTestFile(t, dir, "item-json.go", `package nestedcustompbjson
211
+
212
+ func (x *Item) UnmarshalJSON(b []byte) error {
213
+ x.Config = b
214
+ return nil
215
+ }
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", "nestedcustompbjson", "foo.pb.ts"))
232
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Foo`) {
233
+ t.Fatalf("outer message with nested custom JSON should keep generated JSON body, got:\n%s", binding)
234
+ }
235
+ if !strings.Contains(binding, `generatedFooJSONMarker`) {
236
+ t.Fatalf("outer generated JSON body should be preserved, got:\n%s", binding)
237
+ }
238
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Item`) {
239
+ t.Fatalf("nested custom JSON method should not be replaced, got:\n%s", binding)
240
+ }
241
+ if !strings.Contains(binding, `.Config = b`) {
242
+ t.Fatalf("nested custom JSON method body should be preserved, got:\n%s", binding)
243
+ }
244
+ }
245
+
246
+ func TestProtobufTypeScriptBindingPreservesJSONGraphWithImportedCustomNestedMessage(t *testing.T) {
247
+ dir := t.TempDir()
248
+ writeTestFile(t, dir, "go.mod", "module example.test/importedcustompbjson\n\ngo 1.25\n")
249
+ writeTestFile(t, dir, "inner/inner.pb.go", `package inner
250
+
251
+ type Inner struct {
252
+ Config []byte
253
+ }
254
+ `)
255
+ writeTestFile(t, dir, "inner/inner.pb.ts", `export interface Inner {
256
+ config?: Uint8Array
257
+ }
258
+ export const Inner = {} as any
259
+ `)
260
+ writeTestFile(t, dir, "inner/inner-json.go", `package inner
261
+
262
+ func (x *Inner) UnmarshalJSON(b []byte) error {
263
+ x.Config = b
264
+ return nil
265
+ }
266
+ `)
267
+ writeTestFile(t, dir, "outer.pb.go", `package importedcustompbjson
268
+
269
+ import "example.test/importedcustompbjson/inner"
270
+
271
+ type Outer struct {
272
+ Inner *inner.Inner
273
+ }
274
+
275
+ func generatedOuterJSONMarker(x *Outer) error {
276
+ return nil
277
+ }
278
+
279
+ func (x *Outer) UnmarshalJSON(b []byte) error {
280
+ return generatedOuterJSONMarker(x)
281
+ }
282
+ `)
283
+ writeTestFile(t, dir, "outer.pb.ts", `import type { Inner } from './inner/inner.pb.js'
284
+
285
+ export interface Outer {
286
+ inner?: Inner
287
+ }
288
+ export const Outer = {} as any
289
+ `)
290
+
291
+ out := filepath.Join(dir, "out")
292
+ comp, err := NewCompiler(&Config{
293
+ Dir: dir,
294
+ OutputPath: out,
295
+ ProtobufTypeScriptBinding: true,
296
+ }, nil, nil)
297
+ if err != nil {
298
+ t.Fatal(err)
299
+ }
300
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
301
+ t.Fatalf("compile with protobuf TypeScript binding: %v", err)
302
+ }
303
+
304
+ binding := readTestFile(t, filepath.Join(out, "@goscript", "example.test", "importedcustompbjson", "outer.pb.ts"))
305
+ if strings.Contains(binding, `UnmarshalBoundMessageJSON(Outer`) {
306
+ t.Fatalf("outer message with imported nested custom JSON should keep generated JSON body, got:\n%s", binding)
307
+ }
308
+ if !strings.Contains(binding, `generatedOuterJSONMarker`) {
309
+ t.Fatalf("outer generated JSON body should be preserved, got:\n%s", binding)
310
+ }
311
+ }
312
+
83
313
  func TestProtobufTypeScriptBindingReportsMissingSibling(t *testing.T) {
84
314
  dir := t.TempDir()
85
315
  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,53 @@ 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
+ }
1545
+
1499
1546
  func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
1500
1547
  moduleDir := writePackageGraphFixture(t, map[string]string{
1501
1548
  "go.mod": "module example.test/structs\n\ngo 1.25.3\n",
@@ -2388,6 +2435,7 @@ func TestCompilePackagesEmitsInterfacesMethodValuesTypeSwitchesAndFunctionAssert
2388
2435
  "Read(): string",
2389
2436
  "Close(): string",
2390
2437
  "$.registerInterfaceType(\n\t\"main.ReadCloser\"",
2438
+ "{ name: \"Close\", args: [], returns: [{ name: \"_r0\", type: { kind: $.TypeKind.Basic, name: \"string\" } }] }]\n);",
2391
2439
  "((__receiver) => () => __receiver.Inc())($.pointerValue<Counter>(counter))",
2392
2440
  "$.namedFunction(greet, \"main.Greeter\", ({ kind: $.TypeKind.Function, name: \"main.Greeter\"",
2393
2441
  "params: [{ kind: $.TypeKind.Basic, name: \"string\" }]",
@@ -4353,10 +4401,15 @@ func TestCompilePackagesLowersUnaryBitwiseComplement(t *testing.T) {
4353
4401
  "main.go": strings.Join([]string{
4354
4402
  "package main",
4355
4403
  "var value = 1",
4404
+ "var wide uint64 = 1",
4405
+ "var signed int64 = 1",
4406
+ "func invert(crc uint64) uint64 {",
4407
+ " return ^crc",
4408
+ "}",
4356
4409
  "func main() {",
4357
4410
  " mask := 7",
4358
4411
  " mask &^= 3",
4359
- " println(^value, value &^ 3, mask, 0700)",
4412
+ " println(^value, ^wide, ^signed, invert(wide), value &^ 3, mask, 0700)",
4360
4413
  "}",
4361
4414
  "",
4362
4415
  }, "\n"),
@@ -4377,8 +4430,9 @@ func TestCompilePackagesLowersUnaryBitwiseComplement(t *testing.T) {
4377
4430
  }
4378
4431
  text := string(content)
4379
4432
  for _, want := range []string{
4433
+ "return $.uint($.uint64Xor(crc, -1n), 64)",
4380
4434
  "mask = mask & ~((3))",
4381
- "$.println(~value, value & ~(3), mask, 0o700)",
4435
+ "$.println($.int64Xor(value, -1n), $.uint($.uint64Xor(wide, -1n), 64), $.int($.int64Xor(signed, -1n)), $.uint(invert($.uint(wide, 64)), 64), value & ~(3), mask, 0o700)",
4382
4436
  } {
4383
4437
  if !strings.Contains(text, want) {
4384
4438
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -49,7 +49,8 @@ func TestCompileSourceReportsParseErrors(t *testing.T) {
49
49
  if err == nil {
50
50
  t.Fatal("expected compile error")
51
51
  }
52
- requireCompileDiagnostic(t, err, "goscript/wasm:parse")
52
+ diag := requireCompileDiagnostic(t, err, "goscript/wasm:parse")
53
+ requireDiagnosticPosition(t, diag, "main.go", 2, 12)
53
54
  }
54
55
 
55
56
  func TestCompileSourceReportsUnsupportedImports(t *testing.T) {
@@ -57,10 +58,27 @@ func TestCompileSourceReportsUnsupportedImports(t *testing.T) {
57
58
  if err == nil {
58
59
  t.Fatal("expected compile error")
59
60
  }
60
- requireCompileDiagnostic(t, err, "goscript/wasm:imports-unsupported")
61
+ diag := requireCompileDiagnostic(t, err, "goscript/wasm:imports-unsupported")
62
+ requireDiagnosticPosition(t, diag, "main.go", 3, 8)
61
63
  }
62
64
 
63
- func requireCompileDiagnostic(t *testing.T, err error, code string) {
65
+ func TestCompileSourceReportsTypecheckPositions(t *testing.T) {
66
+ _, err := CompileSource(strings.Join([]string{
67
+ "package main",
68
+ "",
69
+ "func main() {",
70
+ " missing()",
71
+ "}",
72
+ "",
73
+ }, "\n"), "main")
74
+ if err == nil {
75
+ t.Fatal("expected compile error")
76
+ }
77
+ diag := requireCompileDiagnostic(t, err, "goscript/wasm:typecheck")
78
+ requireDiagnosticPosition(t, diag, "main.go", 4, 5)
79
+ }
80
+
81
+ func requireCompileDiagnostic(t *testing.T, err error, code string) compiler.Diagnostic {
64
82
  t.Helper()
65
83
 
66
84
  var compileErr *compiler.CompileError
@@ -72,8 +90,23 @@ func requireCompileDiagnostic(t *testing.T, err error, code string) {
72
90
  if diag.Severity != compiler.DiagnosticSeverityError {
73
91
  t.Fatalf("expected error severity, got %s", diag.Severity)
74
92
  }
75
- return
93
+ return diag
76
94
  }
77
95
  }
78
96
  t.Fatalf("missing diagnostic %q in %#v", code, compileErr.Diagnostics)
97
+ return compiler.Diagnostic{}
98
+ }
99
+
100
+ func requireDiagnosticPosition(t *testing.T, diag compiler.Diagnostic, displayFile string, line int, column int) {
101
+ t.Helper()
102
+
103
+ if diag.Position == nil {
104
+ t.Fatalf("expected position in %#v", diag)
105
+ }
106
+ if diag.Position.DisplayFile != displayFile {
107
+ t.Fatalf("DisplayFile = %q, want %q", diag.Position.DisplayFile, displayFile)
108
+ }
109
+ if diag.Position.Line != line || diag.Position.Column != column {
110
+ t.Fatalf("position = %d:%d, want %d:%d", diag.Position.Line, diag.Position.Column, line, column)
111
+ }
79
112
  }