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.
- package/cmd/goscript-wasm/main.go +38 -6
- package/compiler/diagnostic.go +104 -12
- package/compiler/diagnostic_test.go +106 -0
- package/compiler/gotest/runner.go +1 -17
- package/compiler/gotest/runner_test.go +20 -0
- package/compiler/index.test.ts +23 -0
- package/compiler/lowered-program.go +9 -7
- package/compiler/lowering.go +359 -72
- package/compiler/lowering_bench_test.go +1 -0
- package/compiler/lowering_internal_test.go +18 -0
- package/compiler/protobuf-ts-binding.go +65 -12
- package/compiler/protobuf-ts-binding_test.go +230 -0
- package/compiler/runtime-contract.go +4 -0
- package/compiler/runtime-contract_test.go +2 -0
- package/compiler/service.go +1 -0
- package/compiler/skeleton_test.go +56 -2
- package/compiler/wasm/compile_test.go +37 -4
- package/compiler/wasm-api.go +57 -7
- package/dist/gs/builtin/hostio.js +5 -0
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +11 -1
- package/dist/gs/builtin/slice.js +158 -2
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
- package/dist/gs/internal/byteorder/index.js +2 -2
- package/dist/gs/internal/byteorder/index.js.map +1 -1
- package/dist/gs/reflect/type.js +57 -0
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
- package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
- package/gs/builtin/hostio.test.ts +16 -0
- package/gs/builtin/hostio.ts +7 -0
- package/gs/builtin/runtime-contract.test.ts +28 -0
- package/gs/builtin/slice.ts +225 -20
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
- package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
- package/gs/internal/byteorder/index.test.ts +2 -2
- package/gs/internal/byteorder/index.ts +2 -2
- package/gs/reflect/type.ts +64 -0
- package/gs/reflect/typefor.test.ts +21 -1
- package/gs/sync/atomic/doc_64.gs.ts +6 -7
- package/gs/sync/atomic/doc_64.test.ts +43 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
452
|
-
if
|
|
504
|
+
_, after, ok := strings.Cut(tag, key)
|
|
505
|
+
if !ok {
|
|
453
506
|
return ""
|
|
454
507
|
}
|
|
455
|
-
rest :=
|
|
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,
|
package/compiler/service.go
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
}
|