goscript 0.0.8 → 0.0.11
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/builtin/builtin.ts +277 -1
- package/cmd/goscript/cmd_compile.go +8 -6
- package/compiler/compile.go +28 -11
- package/compiler/compile_decls.go +12 -0
- package/compiler/compile_expr.go +178 -27
- package/compiler/compile_spec.go +285 -5
- package/compiler/compile_stmt.go +160 -49
- package/compiler/writer.go +2 -2
- package/dist/builtin/builtin.d.ts +112 -1
- package/dist/builtin/builtin.js +199 -0
- package/dist/builtin/builtin.js.map +1 -1
- package/dist/compliance/tests/array_literal/array_literal.gs.js +1 -0
- package/dist/compliance/tests/array_literal/array_literal.gs.js.map +1 -1
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.d.ts +1 -0
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js +82 -0
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js.map +1 -0
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js +4 -0
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js +3 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js.map +1 -1
- package/dist/compliance/tests/defer_statement/defer_statement.gs.d.ts +1 -0
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js +75 -0
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js.map +1 -0
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.d.ts +1 -0
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js +37 -0
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js.map +1 -0
- package/dist/compliance/tests/float64/float64.gs.js +1 -0
- package/dist/compliance/tests/float64/float64.gs.js.map +1 -1
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.d.ts +1 -0
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js +14 -0
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js.map +1 -0
- package/dist/compliance/tests/func_literal/func_literal.gs.js.map +1 -1
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.d.ts +2 -0
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js +4 -0
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.d.ts +1 -0
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js +34 -0
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js.map +1 -0
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js +8 -2
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js.map +1 -1
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js +16 -2
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js.map +1 -1
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.d.ts +1 -0
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js +51 -0
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js.map +1 -0
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js +3 -1
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js.map +1 -1
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js +3 -0
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js.map +1 -1
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js +3 -0
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js.map +1 -1
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js +3 -0
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js +3 -0
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js +3 -0
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js +3 -0
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js +3 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js.map +1 -1
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js +1 -0
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js.map +1 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js +3 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/slices/slices.gs.js +280 -7
- package/dist/compliance/tests/slices/slices.gs.js.map +1 -1
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js.map +1 -1
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.d.ts +1 -0
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js +48 -0
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js.map +1 -0
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js +3 -0
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js.map +1 -1
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js +3 -0
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js.map +1 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js +3 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js.map +1 -1
- package/package.json +4 -3
package/compiler/compile_spec.go
CHANGED
|
@@ -3,6 +3,9 @@ package compiler
|
|
|
3
3
|
import (
|
|
4
4
|
"fmt"
|
|
5
5
|
"go/ast"
|
|
6
|
+
"go/types"
|
|
7
|
+
"sort"
|
|
8
|
+
"strings"
|
|
6
9
|
)
|
|
7
10
|
|
|
8
11
|
// WriteSpec writes a specification to the output.
|
|
@@ -24,6 +27,102 @@ func (c *GoToTSCompiler) WriteSpec(a ast.Spec) error {
|
|
|
24
27
|
return nil
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
// collectMethodNames returns a comma-separated string of method names for a struct
|
|
31
|
+
func (c *GoToTSCompiler) collectMethodNames(structName string) string {
|
|
32
|
+
var methodNames []string
|
|
33
|
+
|
|
34
|
+
for _, fileSyntax := range c.pkg.Syntax {
|
|
35
|
+
for _, decl := range fileSyntax.Decls {
|
|
36
|
+
funcDecl, isFunc := decl.(*ast.FuncDecl)
|
|
37
|
+
if !isFunc || funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 {
|
|
38
|
+
continue // Skip non-functions or functions without receivers
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if the receiver type matches the struct name
|
|
42
|
+
recvField := funcDecl.Recv.List[0]
|
|
43
|
+
recvType := recvField.Type
|
|
44
|
+
// Handle pointer receivers (*MyStruct) and value receivers (MyStruct)
|
|
45
|
+
if starExpr, ok := recvType.(*ast.StarExpr); ok {
|
|
46
|
+
recvType = starExpr.X // Get the type being pointed to
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check if the receiver identifier name matches the struct name
|
|
50
|
+
if ident, ok := recvType.(*ast.Ident); ok && ident.Name == structName {
|
|
51
|
+
// Found a method for this struct
|
|
52
|
+
methodNames = append(methodNames, fmt.Sprintf("'%s'", funcDecl.Name.Name))
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return strings.Join(methodNames, ", ")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// getTypeExprName returns a string representation of a type expression.
|
|
61
|
+
func (c *GoToTSCompiler) getTypeExprName(expr ast.Expr) string {
|
|
62
|
+
switch t := expr.(type) {
|
|
63
|
+
case *ast.Ident:
|
|
64
|
+
return t.Name
|
|
65
|
+
case *ast.SelectorExpr:
|
|
66
|
+
return fmt.Sprintf("%s.%s", c.getTypeExprName(t.X), t.Sel.Name)
|
|
67
|
+
case *ast.StarExpr:
|
|
68
|
+
return c.getTypeExprName(t.X) // Unwrap pointer type
|
|
69
|
+
default:
|
|
70
|
+
return "embedded" // Fallback for complex type expressions
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// collectInterfaceMethods returns a comma-separated string of method names for an interface
|
|
75
|
+
func (c *GoToTSCompiler) collectInterfaceMethods(interfaceType *ast.InterfaceType) string {
|
|
76
|
+
// Use a map to ensure uniqueness of method names
|
|
77
|
+
methodNamesMap := make(map[string]struct{})
|
|
78
|
+
|
|
79
|
+
if interfaceType.Methods != nil {
|
|
80
|
+
for _, method := range interfaceType.Methods.List {
|
|
81
|
+
if len(method.Names) > 0 {
|
|
82
|
+
// Named method
|
|
83
|
+
methodNamesMap[method.Names[0].Name] = struct{}{}
|
|
84
|
+
} else {
|
|
85
|
+
// Embedded interface - resolve it and collect its methods
|
|
86
|
+
embeddedType := method.Type
|
|
87
|
+
|
|
88
|
+
// Resolve the embedded interface using type information
|
|
89
|
+
if tv, ok := c.pkg.TypesInfo.Types[embeddedType]; ok && tv.Type != nil {
|
|
90
|
+
// Get the underlying interface type
|
|
91
|
+
var ifaceType *types.Interface
|
|
92
|
+
if named, ok := tv.Type.(*types.Named); ok {
|
|
93
|
+
if underlying, ok := named.Underlying().(*types.Interface); ok {
|
|
94
|
+
ifaceType = underlying
|
|
95
|
+
}
|
|
96
|
+
} else if underlying, ok := tv.Type.(*types.Interface); ok {
|
|
97
|
+
ifaceType = underlying
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Collect methods from the interface
|
|
101
|
+
if ifaceType != nil {
|
|
102
|
+
for i := 0; i < ifaceType.NumMethods(); i++ {
|
|
103
|
+
methodNamesMap[ifaceType.Method(i).Name()] = struct{}{}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Couldn't resolve the embedded interface
|
|
108
|
+
c.tsw.WriteCommentLine("// Note: Some embedded interface methods may not be fully resolved")
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Convert the map to a slice for a deterministic output order
|
|
115
|
+
var methodNames []string
|
|
116
|
+
for name := range methodNamesMap {
|
|
117
|
+
methodNames = append(methodNames, fmt.Sprintf("'%s'", name))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Sort for deterministic output
|
|
121
|
+
sort.Strings(methodNames)
|
|
122
|
+
|
|
123
|
+
return strings.Join(methodNames, ", ")
|
|
124
|
+
}
|
|
125
|
+
|
|
27
126
|
// WriteTypeSpec writes the type specification to the output.
|
|
28
127
|
func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
|
|
29
128
|
if a.Doc != nil {
|
|
@@ -35,11 +134,27 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
|
|
|
35
134
|
|
|
36
135
|
switch t := a.Type.(type) {
|
|
37
136
|
case *ast.StructType:
|
|
38
|
-
//
|
|
137
|
+
// Detect embedded struct (anonymous field) to support Go struct embedding.
|
|
138
|
+
var embeddedExpr ast.Expr
|
|
139
|
+
for _, f := range t.Fields.List {
|
|
140
|
+
if len(f.Names) == 0 { // anonymous field ⇒ embedded type
|
|
141
|
+
embeddedExpr = f.Type
|
|
142
|
+
break // only the first embedded struct is supported for now
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Write the class header, adding "extends Embedded" when an embedded struct is present.
|
|
39
146
|
c.tsw.WriteLiterally("class ")
|
|
40
147
|
if err := c.WriteValueExpr(a.Name); err != nil { // Class name is a value identifier
|
|
41
148
|
return err
|
|
42
149
|
}
|
|
150
|
+
if embeddedExpr != nil {
|
|
151
|
+
// For pointer embedding (*T) unwrap the star so we extend T, not (T|null)
|
|
152
|
+
if star, ok := embeddedExpr.(*ast.StarExpr); ok {
|
|
153
|
+
embeddedExpr = star.X
|
|
154
|
+
}
|
|
155
|
+
c.tsw.WriteLiterally(" extends ")
|
|
156
|
+
c.WriteTypeExpr(embeddedExpr) // Embedded type name
|
|
157
|
+
}
|
|
43
158
|
c.tsw.WriteLine(" {")
|
|
44
159
|
c.tsw.Indent(1)
|
|
45
160
|
|
|
@@ -79,8 +194,51 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
|
|
|
79
194
|
|
|
80
195
|
// constructor and clone using Object.assign for compactness
|
|
81
196
|
c.tsw.WriteLine("")
|
|
82
|
-
|
|
197
|
+
if embeddedExpr != nil {
|
|
198
|
+
// Determine the embedded type's identifier (strip any package prefix)
|
|
199
|
+
embeddedTypeNameFull := c.getTypeExprName(embeddedExpr)
|
|
200
|
+
propName := embeddedTypeNameFull
|
|
201
|
+
if idx := strings.LastIndex(propName, "."); idx != -1 {
|
|
202
|
+
propName = propName[idx+1:] // keep only the last segment for the property key
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The init parameter allows either flattened fields or a nested {Embedded: {...}} object
|
|
206
|
+
c.tsw.WriteLinef(
|
|
207
|
+
"constructor(init?: Partial<%s> & { %s?: Partial<%s> }) {",
|
|
208
|
+
className, propName, propName,
|
|
209
|
+
)
|
|
210
|
+
c.tsw.Indent(1)
|
|
211
|
+
|
|
212
|
+
// Pass either the nested embedded object or the full init directly to super()
|
|
213
|
+
c.tsw.WriteLinef("super(init?.%s || init);", propName)
|
|
214
|
+
|
|
215
|
+
// Copy fields that belong only to this derived class
|
|
216
|
+
c.tsw.WriteLine("if (init) {")
|
|
217
|
+
c.tsw.Indent(1)
|
|
218
|
+
c.tsw.WriteLinef("const { %s, ...rest } = init as any;", propName)
|
|
219
|
+
c.tsw.WriteLine("Object.assign(this, rest);")
|
|
220
|
+
c.tsw.Indent(-1)
|
|
221
|
+
c.tsw.WriteLine("}")
|
|
222
|
+
|
|
223
|
+
c.tsw.Indent(-1)
|
|
224
|
+
c.tsw.WriteLine("}")
|
|
225
|
+
} else {
|
|
226
|
+
// For a base class without embedding, use simple constructor
|
|
227
|
+
c.tsw.WriteLinef("constructor(init?: Partial<%s>) { if (init) Object.assign(this, init as any); }", className)
|
|
228
|
+
}
|
|
83
229
|
c.tsw.WriteLinef("public clone(): %s { return Object.assign(Object.create(%s.prototype) as %s, this); }", className, className, className)
|
|
230
|
+
|
|
231
|
+
// Add code to register the type with the runtime system
|
|
232
|
+
c.tsw.WriteLine("")
|
|
233
|
+
c.tsw.WriteLinef("// Register this type with the runtime type system")
|
|
234
|
+
c.tsw.WriteLinef("static __typeInfo = goscript.registerType(")
|
|
235
|
+
c.tsw.WriteLinef(" '%s',", className)
|
|
236
|
+
c.tsw.WriteLinef(" goscript.TypeKind.Struct,")
|
|
237
|
+
c.tsw.WriteLinef(" new %s(),", className)
|
|
238
|
+
c.tsw.WriteLinef(" new Set([%s]),", c.collectMethodNames(className))
|
|
239
|
+
c.tsw.WriteLinef(" %s", className)
|
|
240
|
+
c.tsw.WriteLinef(");")
|
|
241
|
+
|
|
84
242
|
c.tsw.Indent(-1)
|
|
85
243
|
c.tsw.WriteLine("}")
|
|
86
244
|
case *ast.InterfaceType:
|
|
@@ -88,8 +246,20 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
|
|
|
88
246
|
if err := c.WriteValueExpr(a.Name); err != nil { // Interface name is a value identifier
|
|
89
247
|
return err
|
|
90
248
|
}
|
|
91
|
-
c.tsw.
|
|
92
|
-
c.WriteTypeExpr(a.Type)
|
|
249
|
+
c.tsw.WriteLiterally(" ") // Changed from WriteLine to WriteLiterally
|
|
250
|
+
c.WriteTypeExpr(a.Type) // The interface definition itself is a type
|
|
251
|
+
|
|
252
|
+
// Add code to register the interface with the runtime system
|
|
253
|
+
interfaceName := a.Name.Name
|
|
254
|
+
c.tsw.WriteLine("")
|
|
255
|
+
c.tsw.WriteLinef("// Register this interface with the runtime type system")
|
|
256
|
+
c.tsw.WriteLinef("const %s__typeInfo = goscript.registerType(", interfaceName)
|
|
257
|
+
c.tsw.WriteLinef(" '%s',", interfaceName)
|
|
258
|
+
c.tsw.WriteLinef(" goscript.TypeKind.Interface,")
|
|
259
|
+
c.tsw.WriteLinef(" null,") // Zero value for interface is null
|
|
260
|
+
c.tsw.WriteLinef(" new Set([%s]),", c.collectInterfaceMethods(t))
|
|
261
|
+
c.tsw.WriteLinef(" undefined")
|
|
262
|
+
c.tsw.WriteLinef(");")
|
|
93
263
|
default:
|
|
94
264
|
// type alias
|
|
95
265
|
c.tsw.WriteLiterally("type ")
|
|
@@ -110,8 +280,17 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
|
|
|
110
280
|
c.WriteDoc(decl.Doc)
|
|
111
281
|
}
|
|
112
282
|
|
|
283
|
+
// Determine if method is async by checking for async operations in the body
|
|
284
|
+
isAsync := c.containsAsyncOperations(decl.Body)
|
|
285
|
+
|
|
113
286
|
// Methods are typically public in the TS output
|
|
114
287
|
c.tsw.WriteLiterally("public ")
|
|
288
|
+
|
|
289
|
+
// Add async modifier if needed
|
|
290
|
+
if isAsync {
|
|
291
|
+
c.tsw.WriteLiterally("async ")
|
|
292
|
+
}
|
|
293
|
+
|
|
115
294
|
// Keep original Go casing for method names
|
|
116
295
|
if err := c.WriteValueExpr(decl.Name); err != nil { // Method name is a value identifier
|
|
117
296
|
return err
|
|
@@ -129,6 +308,9 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
|
|
|
129
308
|
// Handle return type
|
|
130
309
|
if funcType.Results != nil && len(funcType.Results.List) > 0 {
|
|
131
310
|
c.tsw.WriteLiterally(": ")
|
|
311
|
+
if isAsync {
|
|
312
|
+
c.tsw.WriteLiterally("Promise<")
|
|
313
|
+
}
|
|
132
314
|
if len(funcType.Results.List) == 1 {
|
|
133
315
|
// Single return value
|
|
134
316
|
resultType := funcType.Results.List[0].Type
|
|
@@ -144,12 +326,27 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
|
|
|
144
326
|
}
|
|
145
327
|
c.tsw.WriteLiterally("]")
|
|
146
328
|
}
|
|
329
|
+
if isAsync {
|
|
330
|
+
c.tsw.WriteLiterally(">")
|
|
331
|
+
}
|
|
147
332
|
} else {
|
|
148
333
|
// No return value -> void
|
|
149
|
-
|
|
334
|
+
if isAsync {
|
|
335
|
+
c.tsw.WriteLiterally(": Promise<void>")
|
|
336
|
+
} else {
|
|
337
|
+
c.tsw.WriteLiterally(": void")
|
|
338
|
+
}
|
|
150
339
|
}
|
|
151
340
|
|
|
152
341
|
c.tsw.WriteLiterally(" ")
|
|
342
|
+
|
|
343
|
+
// Check if function body has defer statements
|
|
344
|
+
c.nextBlockNeedsDefer = c.scanForDefer(decl.Body)
|
|
345
|
+
|
|
346
|
+
// Save previous async state and set current state based on isAsync
|
|
347
|
+
previousAsyncState := c.inAsyncFunction
|
|
348
|
+
c.inAsyncFunction = isAsync
|
|
349
|
+
|
|
153
350
|
// Bind receiver name to this
|
|
154
351
|
if recvField := decl.Recv.List[0]; len(recvField.Names) > 0 {
|
|
155
352
|
recvName := recvField.Names[0].Name
|
|
@@ -157,21 +354,40 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
|
|
|
157
354
|
c.tsw.WriteLine("{")
|
|
158
355
|
c.tsw.Indent(1)
|
|
159
356
|
c.tsw.WriteLinef("const %s = this", recvName)
|
|
357
|
+
|
|
358
|
+
// Add using statement if needed
|
|
359
|
+
if c.nextBlockNeedsDefer {
|
|
360
|
+
if c.inAsyncFunction {
|
|
361
|
+
c.tsw.WriteLine("await using __defer = new goscript.AsyncDisposableStack();")
|
|
362
|
+
} else {
|
|
363
|
+
c.tsw.WriteLine("using cleanup = new goscript.DisposableStack();")
|
|
364
|
+
}
|
|
365
|
+
c.nextBlockNeedsDefer = false
|
|
366
|
+
}
|
|
367
|
+
|
|
160
368
|
// write method body without outer braces
|
|
161
369
|
for _, stmt := range decl.Body.List {
|
|
162
370
|
if err := c.WriteStmt(stmt); err != nil {
|
|
371
|
+
c.inAsyncFunction = previousAsyncState // Restore state before returning error
|
|
163
372
|
return fmt.Errorf("failed to write statement in function body: %w", err)
|
|
164
373
|
}
|
|
165
374
|
}
|
|
166
375
|
c.tsw.Indent(-1)
|
|
167
376
|
c.tsw.WriteLine("}")
|
|
377
|
+
|
|
378
|
+
// Restore previous async state
|
|
379
|
+
c.inAsyncFunction = previousAsyncState
|
|
168
380
|
return nil
|
|
169
381
|
}
|
|
170
382
|
}
|
|
171
383
|
// no named receiver, write whole body
|
|
172
384
|
if err := c.WriteStmt(decl.Body); err != nil {
|
|
385
|
+
c.inAsyncFunction = previousAsyncState // Restore state before returning error
|
|
173
386
|
return fmt.Errorf("failed to write function body: %w", err)
|
|
174
387
|
}
|
|
388
|
+
|
|
389
|
+
// Restore previous async state
|
|
390
|
+
c.inAsyncFunction = previousAsyncState
|
|
175
391
|
return nil
|
|
176
392
|
}
|
|
177
393
|
|
|
@@ -254,3 +470,67 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
|
254
470
|
|
|
255
471
|
c.tsw.WriteImport(impName, importPath)
|
|
256
472
|
}
|
|
473
|
+
|
|
474
|
+
// WriteInterfaceMethodSignature writes a TypeScript interface method signature from a Go ast.Field.
|
|
475
|
+
func (c *GoToTSCompiler) WriteInterfaceMethodSignature(field *ast.Field) error {
|
|
476
|
+
if len(field.Names) == 0 {
|
|
477
|
+
// Should not happen for named methods in an interface, but handle defensively
|
|
478
|
+
return fmt.Errorf("interface method field has no name")
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
methodName := field.Names[0]
|
|
482
|
+
funcType, ok := field.Type.(*ast.FuncType)
|
|
483
|
+
if !ok {
|
|
484
|
+
// Should not happen for valid interface methods, but handle defensively
|
|
485
|
+
c.tsw.WriteCommentInline("unexpected interface method type")
|
|
486
|
+
return fmt.Errorf("interface method type is not a FuncType")
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Write method name
|
|
490
|
+
c.WriteIdentValue(methodName)
|
|
491
|
+
|
|
492
|
+
// Write parameter list (name: type)
|
|
493
|
+
c.tsw.WriteLiterally("(")
|
|
494
|
+
if funcType.Params != nil {
|
|
495
|
+
for i, param := range funcType.Params.List {
|
|
496
|
+
if i > 0 {
|
|
497
|
+
c.tsw.WriteLiterally(", ")
|
|
498
|
+
}
|
|
499
|
+
// Determine parameter name
|
|
500
|
+
paramName := fmt.Sprintf("_p%d", i) // Default placeholder
|
|
501
|
+
if len(param.Names) > 0 && param.Names[0].Name != "" && param.Names[0].Name != "_" {
|
|
502
|
+
paramName = param.Names[0].Name
|
|
503
|
+
}
|
|
504
|
+
c.tsw.WriteLiterally(paramName)
|
|
505
|
+
c.tsw.WriteLiterally(": ")
|
|
506
|
+
c.WriteTypeExpr(param.Type)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
c.tsw.WriteLiterally(")")
|
|
510
|
+
|
|
511
|
+
// Write return type
|
|
512
|
+
// Use WriteFuncType's logic for return types, but without the async handling
|
|
513
|
+
if funcType.Results != nil && len(funcType.Results.List) > 0 {
|
|
514
|
+
c.tsw.WriteLiterally(": ")
|
|
515
|
+
if len(funcType.Results.List) == 1 && len(funcType.Results.List[0].Names) == 0 {
|
|
516
|
+
// Single unnamed return type
|
|
517
|
+
c.WriteTypeExpr(funcType.Results.List[0].Type)
|
|
518
|
+
} else {
|
|
519
|
+
// Multiple or named return types -> tuple
|
|
520
|
+
c.tsw.WriteLiterally("[")
|
|
521
|
+
for i, result := range funcType.Results.List {
|
|
522
|
+
if i > 0 {
|
|
523
|
+
c.tsw.WriteLiterally(", ")
|
|
524
|
+
}
|
|
525
|
+
c.WriteTypeExpr(result.Type)
|
|
526
|
+
}
|
|
527
|
+
c.tsw.WriteLiterally("]")
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
// No return value -> void
|
|
531
|
+
c.tsw.WriteLiterally(": void")
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
c.tsw.WriteLine(";") // Semicolon at the end of the method signature
|
|
535
|
+
return nil
|
|
536
|
+
}
|
package/compiler/compile_stmt.go
CHANGED
|
@@ -28,6 +28,10 @@ func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
|
|
|
28
28
|
if err := c.WriteStmtReturn(exp); err != nil {
|
|
29
29
|
return fmt.Errorf("failed to write return statement: %w", err)
|
|
30
30
|
}
|
|
31
|
+
case *ast.DeferStmt:
|
|
32
|
+
if err := c.WriteDeferStmt(exp); err != nil {
|
|
33
|
+
return fmt.Errorf("failed to write defer statement: %w", err)
|
|
34
|
+
}
|
|
31
35
|
case *ast.IfStmt:
|
|
32
36
|
if err := c.WriteStmtIf(exp); err != nil {
|
|
33
37
|
return fmt.Errorf("failed to write if statement: %w", err)
|
|
@@ -125,12 +129,70 @@ func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
|
|
|
125
129
|
if err := c.WriteStmtSelect(exp); err != nil {
|
|
126
130
|
return fmt.Errorf("failed to write select statement: %w", err)
|
|
127
131
|
}
|
|
132
|
+
case *ast.BranchStmt:
|
|
133
|
+
// Handle break and continue statements
|
|
134
|
+
switch exp.Tok {
|
|
135
|
+
case token.BREAK:
|
|
136
|
+
c.tsw.WriteLine("break") // No semicolon needed
|
|
137
|
+
case token.CONTINUE:
|
|
138
|
+
c.tsw.WriteLine("continue") // No semicolon needed
|
|
139
|
+
default:
|
|
140
|
+
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled branch statement token: %s", exp.Tok.String()))
|
|
141
|
+
}
|
|
128
142
|
default:
|
|
129
143
|
c.tsw.WriteCommentLine(fmt.Sprintf("unknown statement: %s\n", litter.Sdump(a)))
|
|
130
144
|
}
|
|
131
145
|
return nil
|
|
132
146
|
}
|
|
133
147
|
|
|
148
|
+
// WriteDeferStmt writes a defer statement using TypeScript's DisposableStack.
|
|
149
|
+
func (c *GoToTSCompiler) WriteDeferStmt(exp *ast.DeferStmt) error {
|
|
150
|
+
// Check if deferred call contains async operations
|
|
151
|
+
hasAsyncOps := c.containsAsyncOperations(exp.Call)
|
|
152
|
+
|
|
153
|
+
// Choose the right stack variable and async modifier
|
|
154
|
+
stackVar := "cleanup"
|
|
155
|
+
asyncPrefix := ""
|
|
156
|
+
|
|
157
|
+
if c.inAsyncFunction {
|
|
158
|
+
stackVar = "__defer"
|
|
159
|
+
if hasAsyncOps {
|
|
160
|
+
asyncPrefix = "async "
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
c.tsw.WriteLiterally(fmt.Sprintf("%s.defer(%s() => {", stackVar, asyncPrefix))
|
|
165
|
+
c.tsw.Indent(1)
|
|
166
|
+
c.tsw.WriteLine("")
|
|
167
|
+
|
|
168
|
+
// Write the deferred call or inline the body when it's an immediately-invoked
|
|
169
|
+
// function literal (defer func(){ ... }()).
|
|
170
|
+
if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok && len(exp.Call.Args) == 0 {
|
|
171
|
+
// Inline the function literal's body to avoid nested arrow invocation.
|
|
172
|
+
prevAsyncState := c.inAsyncFunction
|
|
173
|
+
c.inAsyncFunction = hasAsyncOps
|
|
174
|
+
|
|
175
|
+
for _, stmt := range funcLit.Body.List {
|
|
176
|
+
if err := c.WriteStmt(stmt); err != nil {
|
|
177
|
+
c.inAsyncFunction = prevAsyncState
|
|
178
|
+
return fmt.Errorf("failed to write statement in deferred function body: %w", err)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
c.inAsyncFunction = prevAsyncState
|
|
183
|
+
} else {
|
|
184
|
+
// Fallback: write the call expression as-is.
|
|
185
|
+
if err := c.WriteValueExpr(exp.Call); err != nil {
|
|
186
|
+
return fmt.Errorf("failed to write deferred call: %w", err)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
c.tsw.Indent(-1)
|
|
191
|
+
c.tsw.WriteLine("});")
|
|
192
|
+
|
|
193
|
+
return nil
|
|
194
|
+
}
|
|
195
|
+
|
|
134
196
|
// WriteStmtSelect writes a select statement.
|
|
135
197
|
func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
|
|
136
198
|
// This is our implementation of the select statement, which will use Promise.race
|
|
@@ -435,10 +497,12 @@ func (s *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
|
|
|
435
497
|
s.tsw.WriteLiterally(" else ")
|
|
436
498
|
switch elseStmt := exp.Else.(type) {
|
|
437
499
|
case *ast.BlockStmt:
|
|
500
|
+
// Always pass false for suppressNewline here
|
|
438
501
|
if err := s.WriteStmtBlock(elseStmt, false); err != nil {
|
|
439
502
|
return fmt.Errorf("failed to write else block statement in if statement: %w", err)
|
|
440
503
|
}
|
|
441
504
|
case *ast.IfStmt:
|
|
505
|
+
// Recursive call handles its own block formatting
|
|
442
506
|
if err := s.WriteStmtIf(elseStmt); err != nil {
|
|
443
507
|
return fmt.Errorf("failed to write else if statement in if statement: %w", err)
|
|
444
508
|
}
|
|
@@ -482,6 +546,16 @@ func (c *GoToTSCompiler) WriteStmtBlock(exp *ast.BlockStmt, suppressNewline bool
|
|
|
482
546
|
c.tsw.WriteLine("{")
|
|
483
547
|
c.tsw.Indent(1)
|
|
484
548
|
|
|
549
|
+
// Add "using" statement if needed
|
|
550
|
+
if c.nextBlockNeedsDefer {
|
|
551
|
+
if c.inAsyncFunction {
|
|
552
|
+
c.tsw.WriteLine("await using __defer = new goscript.AsyncDisposableStack();")
|
|
553
|
+
} else {
|
|
554
|
+
c.tsw.WriteLine("using cleanup = new goscript.DisposableStack();")
|
|
555
|
+
}
|
|
556
|
+
c.nextBlockNeedsDefer = false
|
|
557
|
+
}
|
|
558
|
+
|
|
485
559
|
// Prepare line info
|
|
486
560
|
var file *token.File
|
|
487
561
|
if c.pkg != nil && c.pkg.Fset != nil && exp.Lbrace.IsValid() {
|
|
@@ -775,32 +849,6 @@ func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *as
|
|
|
775
849
|
|
|
776
850
|
func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
777
851
|
// writeTypeAssertion handles multi-variable assignment from a type assertion.
|
|
778
|
-
writeTypeAssertion := func(typeAssertExpr *ast.TypeAssertExpr) error {
|
|
779
|
-
interfaceExpr := typeAssertExpr.X
|
|
780
|
-
assertedType := typeAssertExpr.Type
|
|
781
|
-
|
|
782
|
-
// Write the TypeScript code for the type assertion.
|
|
783
|
-
c.tsw.WriteLiterally("let ok: boolean = (")
|
|
784
|
-
if err := c.WriteValueExpr(interfaceExpr); err != nil {
|
|
785
|
-
return fmt.Errorf("failed to write interface expression in type assertion (ok check): %w", err)
|
|
786
|
-
}
|
|
787
|
-
c.tsw.WriteLiterally(" as any) satisfies ")
|
|
788
|
-
c.WriteTypeExpr(assertedType)
|
|
789
|
-
c.tsw.WriteLine("")
|
|
790
|
-
|
|
791
|
-
c.tsw.WriteLiterally("let assertedValue: ")
|
|
792
|
-
c.WriteTypeExpr(assertedType)
|
|
793
|
-
c.tsw.WriteLiterally(" | null = ok ? (")
|
|
794
|
-
if err := c.WriteValueExpr(interfaceExpr); err != nil {
|
|
795
|
-
return fmt.Errorf("failed to write interface expression in type assertion (value assignment): %w", err)
|
|
796
|
-
}
|
|
797
|
-
c.tsw.WriteLiterally(" as ")
|
|
798
|
-
c.WriteTypeExpr(assertedType)
|
|
799
|
-
c.tsw.WriteLiterally(") : null")
|
|
800
|
-
c.tsw.WriteLine("")
|
|
801
|
-
|
|
802
|
-
return nil
|
|
803
|
-
}
|
|
804
852
|
|
|
805
853
|
// writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
|
|
806
854
|
writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
|
|
@@ -982,7 +1030,7 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
982
1030
|
if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
|
|
983
1031
|
rhsExpr := exp.Rhs[0]
|
|
984
1032
|
if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
|
|
985
|
-
return writeTypeAssertion(typeAssertExpr)
|
|
1033
|
+
return c.writeTypeAssertion(exp.Lhs, typeAssertExpr, exp.Tok)
|
|
986
1034
|
} else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
|
|
987
1035
|
// Check if this is a map lookup (comma-ok idiom)
|
|
988
1036
|
if len(exp.Lhs) == 2 {
|
|
@@ -1458,41 +1506,104 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
|
|
|
1458
1506
|
return fmt.Errorf("unsupported range loop type: %T", underlying)
|
|
1459
1507
|
}
|
|
1460
1508
|
|
|
1509
|
+
// WriteStmtSend writes a channel send statement (ch <- value).
|
|
1510
|
+
func (c *GoToTSCompiler) WriteStmtSend(exp *ast.SendStmt) error {
|
|
1511
|
+
// Translate ch <- value to await ch.send(value)
|
|
1512
|
+
c.tsw.WriteLiterally("await ")
|
|
1513
|
+
if err := c.WriteValueExpr(exp.Chan); err != nil { // The channel expression
|
|
1514
|
+
return fmt.Errorf("failed to write channel expression in send statement: %w", err)
|
|
1515
|
+
}
|
|
1516
|
+
c.tsw.WriteLiterally(".send(")
|
|
1517
|
+
if err := c.WriteValueExpr(exp.Value); err != nil { // The value expression
|
|
1518
|
+
return fmt.Errorf("failed to write value expression in send statement: %w", err)
|
|
1519
|
+
}
|
|
1520
|
+
c.tsw.WriteLiterally(")")
|
|
1521
|
+
c.tsw.WriteLine("") // Add newline after the statement
|
|
1522
|
+
return nil
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1461
1525
|
// isSlice returns true if the underlying type is a slice.
|
|
1462
1526
|
func isSlice(typ gtypes.Type) bool {
|
|
1463
1527
|
_, ok := typ.(*gtypes.Slice)
|
|
1464
1528
|
return ok
|
|
1465
1529
|
}
|
|
1466
1530
|
|
|
1467
|
-
//
|
|
1468
|
-
func
|
|
1469
|
-
|
|
1470
|
-
|
|
1531
|
+
// writeTypeAssertion handles multi-variable assignment from a type assertion.
|
|
1532
|
+
func (c *GoToTSCompiler) writeTypeAssertion(lhs []ast.Expr, typeAssertExpr *ast.TypeAssertExpr, tok token.Token) error {
|
|
1533
|
+
interfaceExpr := typeAssertExpr.X
|
|
1534
|
+
assertedType := typeAssertExpr.Type
|
|
1535
|
+
|
|
1536
|
+
// Ensure LHS has exactly two expressions (value and ok)
|
|
1537
|
+
if len(lhs) != 2 {
|
|
1538
|
+
return fmt.Errorf("type assertion assignment requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
1471
1539
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1540
|
+
|
|
1541
|
+
// Get variable names, handling blank identifiers
|
|
1542
|
+
valueIsBlank := false
|
|
1543
|
+
okIsBlank := false
|
|
1544
|
+
var valueName string
|
|
1545
|
+
var okName string
|
|
1546
|
+
|
|
1547
|
+
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
1548
|
+
if valIdent.Name == "_" {
|
|
1549
|
+
valueIsBlank = true
|
|
1550
|
+
} else {
|
|
1551
|
+
valueName = valIdent.Name
|
|
1552
|
+
}
|
|
1553
|
+
} else {
|
|
1554
|
+
return fmt.Errorf("unhandled LHS expression type for value in type assertion: %T", lhs[0])
|
|
1475
1555
|
}
|
|
1476
|
-
|
|
1477
|
-
if
|
|
1478
|
-
|
|
1556
|
+
|
|
1557
|
+
if okIdent, ok := lhs[1].(*ast.Ident); ok {
|
|
1558
|
+
if okIdent.Name == "_" {
|
|
1559
|
+
okIsBlank = true
|
|
1560
|
+
} else {
|
|
1561
|
+
okName = okIdent.Name
|
|
1562
|
+
}
|
|
1563
|
+
} else {
|
|
1564
|
+
return fmt.Errorf("unhandled LHS expression type for ok in type assertion: %T", lhs[1])
|
|
1479
1565
|
}
|
|
1480
|
-
_, isMap := tv.Type.Underlying().(*gtypes.Map)
|
|
1481
|
-
return isMap
|
|
1482
|
-
}
|
|
1483
1566
|
|
|
1484
|
-
//
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
if
|
|
1489
|
-
|
|
1567
|
+
// Get the type name string for the asserted type
|
|
1568
|
+
typeName := c.getTypeNameString(assertedType)
|
|
1569
|
+
|
|
1570
|
+
// Generate the destructuring assignment
|
|
1571
|
+
if tok == token.DEFINE {
|
|
1572
|
+
c.tsw.WriteLiterally("let ")
|
|
1573
|
+
} else {
|
|
1574
|
+
// We must wrap in parenthesis.
|
|
1575
|
+
c.tsw.WriteLiterally("(")
|
|
1490
1576
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1577
|
+
|
|
1578
|
+
c.tsw.WriteLiterally("{ ")
|
|
1579
|
+
// Dynamically build the destructuring pattern
|
|
1580
|
+
parts := []string{}
|
|
1581
|
+
if !valueIsBlank {
|
|
1582
|
+
parts = append(parts, fmt.Sprintf("value: %s", valueName))
|
|
1583
|
+
}
|
|
1584
|
+
if !okIsBlank {
|
|
1585
|
+
parts = append(parts, fmt.Sprintf("ok: %s", okName))
|
|
1586
|
+
}
|
|
1587
|
+
c.tsw.WriteLiterally(strings.Join(parts, ", "))
|
|
1588
|
+
c.tsw.WriteLiterally(" } = goscript.typeAssert<")
|
|
1589
|
+
|
|
1590
|
+
// Write the asserted type for the generic
|
|
1591
|
+
c.WriteTypeExpr(assertedType)
|
|
1592
|
+
c.tsw.WriteLiterally(">(")
|
|
1593
|
+
|
|
1594
|
+
// Write the interface expression
|
|
1595
|
+
if err := c.WriteValueExpr(interfaceExpr); err != nil {
|
|
1596
|
+
return fmt.Errorf("failed to write interface expression in type assertion call: %w", err)
|
|
1494
1597
|
}
|
|
1598
|
+
c.tsw.WriteLiterally(", ")
|
|
1599
|
+
c.tsw.WriteLiterally(fmt.Sprintf("'%s'", typeName))
|
|
1495
1600
|
c.tsw.WriteLiterally(")")
|
|
1601
|
+
|
|
1602
|
+
if tok != token.DEFINE {
|
|
1603
|
+
c.tsw.WriteLiterally(")")
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1496
1606
|
c.tsw.WriteLine("") // Add newline after the statement
|
|
1607
|
+
|
|
1497
1608
|
return nil
|
|
1498
1609
|
}
|
package/compiler/writer.go
CHANGED
|
@@ -54,7 +54,7 @@ func (w *TSCodeWriter) Indent(count int) {
|
|
|
54
54
|
|
|
55
55
|
// WriteImport writes a TypeScript import.
|
|
56
56
|
func (w *TSCodeWriter) WriteImport(symbolName, importPath string) {
|
|
57
|
-
w.WriteLinef("import * as %s from %q
|
|
57
|
+
w.WriteLinef("import * as %s from %q", symbolName, importPath)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// WriteCommentLine writes a comment as a // line.
|
|
@@ -78,7 +78,7 @@ func (w *TSCodeWriter) WriteLiterally(literal string) {
|
|
|
78
78
|
if w.lineWritten {
|
|
79
79
|
w.WriteLinePreamble()
|
|
80
80
|
}
|
|
81
|
-
w.w.Write([]byte(literal))
|
|
81
|
+
w.w.Write([]byte(literal)) //nolint:errcheck
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// WriteSectionTail writes the end of a section.
|