goscript 0.0.22 → 0.0.23
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/cmd_compile.go +2 -2
- package/compiler/analysis.go +229 -51
- package/compiler/assignment.go +6 -1
- package/compiler/compiler.go +41 -18
- package/compiler/compiler_test.go +36 -8
- package/compiler/composite-lit.go +25 -10
- package/compiler/decl.go +36 -0
- package/compiler/expr-call.go +116 -60
- package/compiler/expr-selector.go +22 -2
- package/compiler/expr-type.go +131 -4
- package/compiler/expr-value.go +7 -37
- package/compiler/expr.go +247 -12
- package/compiler/field.go +3 -3
- package/compiler/lit.go +34 -2
- package/compiler/primitive.go +8 -2
- package/compiler/spec-struct.go +137 -6
- package/compiler/spec-value.go +50 -18
- package/compiler/spec.go +12 -3
- package/compiler/stmt-assign.go +29 -1
- package/compiler/stmt-range.go +9 -11
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +129 -244
- package/compiler/type-assert.go +125 -379
- package/compiler/type.go +187 -125
- package/package.json +5 -5
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2379
- package/dist/builtin/builtin.d.ts +0 -513
- package/dist/builtin/builtin.js +0 -1686
- package/dist/builtin/builtin.js.map +0 -1
package/compiler/spec-value.go
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
package compiler
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"fmt"
|
|
4
5
|
"go/ast"
|
|
5
6
|
"go/token"
|
|
6
|
-
|
|
7
|
-
"github.com/pkg/errors"
|
|
7
|
+
"go/types"
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
// WriteValueSpec translates a Go value specification (`ast.ValueSpec`),
|
|
@@ -44,36 +44,67 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
|
|
|
44
44
|
name := a.Names[0]
|
|
45
45
|
obj := c.pkg.TypesInfo.Defs[name]
|
|
46
46
|
if obj == nil {
|
|
47
|
-
return
|
|
47
|
+
return fmt.Errorf("could not resolve type: %v", name)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
goType := obj.Type()
|
|
51
51
|
needsBox := c.analysis.NeedsBoxed(obj) // Check if address is taken
|
|
52
52
|
|
|
53
|
+
hasInitializer := len(a.Values) > 0
|
|
54
|
+
var initializerExpr ast.Expr
|
|
55
|
+
if hasInitializer {
|
|
56
|
+
initializerExpr = a.Values[0]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if the initializer will result in an $.arrayToSlice call in TypeScript
|
|
60
|
+
isSliceConversion := false
|
|
61
|
+
if hasInitializer {
|
|
62
|
+
// Case 1: Direct call to $.arrayToSlice in Go source (less common for typical array literals)
|
|
63
|
+
if callExpr, isCallExpr := initializerExpr.(*ast.CallExpr); isCallExpr {
|
|
64
|
+
if selExpr, isSelExpr := callExpr.Fun.(*ast.SelectorExpr); isSelExpr {
|
|
65
|
+
if pkgIdent, isPkgIdent := selExpr.X.(*ast.Ident); isPkgIdent && pkgIdent.Name == "$" {
|
|
66
|
+
if selExpr.Sel.Name == "arrayToSlice" {
|
|
67
|
+
isSliceConversion = true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Case 2: Go array or slice literal, which will be compiled to $.arrayToSlice
|
|
74
|
+
// We also check if the original Go type is actually a slice or array.
|
|
75
|
+
if !isSliceConversion { // Only check if not already determined by Case 1
|
|
76
|
+
if _, isCompositeLit := initializerExpr.(*ast.CompositeLit); isCompositeLit {
|
|
77
|
+
switch goType.Underlying().(type) {
|
|
78
|
+
case *types.Slice, *types.Array:
|
|
79
|
+
isSliceConversion = true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
53
85
|
// Start declaration
|
|
54
86
|
c.tsw.WriteLiterally("let ")
|
|
55
87
|
c.tsw.WriteLiterally(name.Name)
|
|
56
|
-
c.tsw.WriteLiterally(": ")
|
|
57
88
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
if !isSliceConversion {
|
|
90
|
+
c.tsw.WriteLiterally(": ")
|
|
91
|
+
// Write type annotation
|
|
92
|
+
if needsBox {
|
|
93
|
+
// If boxed, the variable holds Box<OriginalGoType>
|
|
94
|
+
c.tsw.WriteLiterally("$.Box<")
|
|
95
|
+
c.WriteGoType(goType, GoTypeContextGeneral) // Write the original Go type T
|
|
96
|
+
c.tsw.WriteLiterally(">")
|
|
97
|
+
} else {
|
|
98
|
+
// If not boxed, the variable holds the translated Go type directly
|
|
99
|
+
c.WriteGoType(goType, GoTypeContextGeneral)
|
|
100
|
+
}
|
|
67
101
|
}
|
|
68
102
|
|
|
69
103
|
// Write initializer
|
|
70
104
|
c.tsw.WriteLiterally(" = ")
|
|
71
|
-
hasInitializer := len(a.Values) > 0
|
|
72
|
-
var initializerExpr ast.Expr
|
|
73
|
-
if hasInitializer {
|
|
74
|
-
initializerExpr = a.Values[0]
|
|
75
105
|
|
|
76
|
-
|
|
106
|
+
// Special case for nil pointer to struct type: (*struct{})(nil)
|
|
107
|
+
if hasInitializer {
|
|
77
108
|
if callExpr, isCallExpr := initializerExpr.(*ast.CallExpr); isCallExpr {
|
|
78
109
|
if starExpr, isStarExpr := callExpr.Fun.(*ast.StarExpr); isStarExpr {
|
|
79
110
|
if _, isStructType := starExpr.X.(*ast.StructType); isStructType {
|
|
@@ -81,6 +112,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
|
|
|
81
112
|
if len(callExpr.Args) == 1 {
|
|
82
113
|
if nilIdent, isIdent := callExpr.Args[0].(*ast.Ident); isIdent && nilIdent.Name == "nil" {
|
|
83
114
|
c.tsw.WriteLiterally("null")
|
|
115
|
+
c.tsw.WriteLine("") // Ensure newline after null
|
|
84
116
|
return nil
|
|
85
117
|
}
|
|
86
118
|
}
|
package/compiler/spec.go
CHANGED
|
@@ -253,11 +253,20 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
|
253
253
|
impName = a.Name.Name
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
// All Go package imports are mapped to the @goscript/ scope.
|
|
257
|
+
// The TypeScript compiler will resolve these using tsconfig paths to either
|
|
258
|
+
// handwritten versions (in .goscript-assets) or transpiled versions (in goscript).
|
|
259
|
+
var tsImportPath string
|
|
260
|
+
if goPath == "github.com/aperturerobotics/goscript/builtin" {
|
|
261
|
+
tsImportPath = "@goscript/builtin/builtin.js"
|
|
262
|
+
} else {
|
|
263
|
+
tsImportPath = "@goscript/" + goPath
|
|
264
|
+
}
|
|
265
|
+
|
|
257
266
|
c.analysis.Imports[impName] = &fileImport{
|
|
258
|
-
importPath:
|
|
267
|
+
importPath: tsImportPath,
|
|
259
268
|
importVars: make(map[string]struct{}),
|
|
260
269
|
}
|
|
261
270
|
|
|
262
|
-
c.tsw.WriteImport(impName,
|
|
271
|
+
c.tsw.WriteImport(impName, tsImportPath+"/index.js")
|
|
263
272
|
}
|
package/compiler/stmt-assign.go
CHANGED
|
@@ -5,6 +5,7 @@ import (
|
|
|
5
5
|
"go/ast"
|
|
6
6
|
"go/token"
|
|
7
7
|
"go/types"
|
|
8
|
+
"strings"
|
|
8
9
|
|
|
9
10
|
"github.com/pkg/errors"
|
|
10
11
|
)
|
|
@@ -130,7 +131,7 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
130
131
|
// Add type annotation if we have type information
|
|
131
132
|
if i < len(resultTypes) {
|
|
132
133
|
c.tsw.WriteLiterally(": ")
|
|
133
|
-
c.WriteGoType(resultTypes[i].Type())
|
|
134
|
+
c.WriteGoType(resultTypes[i].Type(), GoTypeContextGeneral)
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
c.tsw.WriteLine("")
|
|
@@ -392,6 +393,8 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
392
393
|
if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, false); err != nil {
|
|
393
394
|
return err
|
|
394
395
|
}
|
|
396
|
+
// Handle potential inline comment for multi-variable assignment
|
|
397
|
+
c.writeInlineComment(exp)
|
|
395
398
|
c.tsw.WriteLine("") // Add newline after the statement
|
|
396
399
|
return nil
|
|
397
400
|
}
|
|
@@ -402,6 +405,8 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
402
405
|
if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
|
|
403
406
|
return err
|
|
404
407
|
}
|
|
408
|
+
// Handle potential inline comment for single assignment
|
|
409
|
+
c.writeInlineComment(exp)
|
|
405
410
|
c.tsw.WriteLine("") // Add newline after the statement
|
|
406
411
|
return nil
|
|
407
412
|
}
|
|
@@ -409,3 +414,26 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
409
414
|
// Should not reach here if LHS/RHS counts are valid and handled
|
|
410
415
|
return fmt.Errorf("unhandled assignment case")
|
|
411
416
|
}
|
|
417
|
+
|
|
418
|
+
// writeInlineComment checks for and writes any inline comments associated with the given AST node.
|
|
419
|
+
// It is intended to be called immediately after writing the main statement/expression.
|
|
420
|
+
func (c *GoToTSCompiler) writeInlineComment(node ast.Node) {
|
|
421
|
+
if c.pkg == nil || c.pkg.Fset == nil || !node.End().IsValid() {
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
file := c.pkg.Fset.File(node.End())
|
|
426
|
+
if file == nil {
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
endLine := file.Line(node.End())
|
|
431
|
+
// Check comments associated *directly* with the node
|
|
432
|
+
for _, cg := range c.analysis.Cmap[node] {
|
|
433
|
+
if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > node.End() {
|
|
434
|
+
commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
|
|
435
|
+
c.tsw.WriteLiterally(" // " + commentText)
|
|
436
|
+
return // Only write the first inline comment found
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
package/compiler/stmt-range.go
CHANGED
|
@@ -128,6 +128,11 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
|
|
|
128
128
|
c.tsw.WriteLine("}")
|
|
129
129
|
return nil
|
|
130
130
|
} else if basic.Info()&types.IsInteger != 0 {
|
|
131
|
+
// The value variable is not allowed ranging over an integer.
|
|
132
|
+
if exp.Value != nil {
|
|
133
|
+
return errors.Errorf("ranging over an integer supports key variable only (not value variable): %v", exp)
|
|
134
|
+
}
|
|
135
|
+
|
|
131
136
|
// Handle ranging over an integer (Go 1.22+)
|
|
132
137
|
// Determine the index variable name for the generated loop
|
|
133
138
|
indexVarName := "_i" // Default name
|
|
@@ -141,20 +146,13 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
|
|
|
141
146
|
if err := c.WriteValueExpr(exp.X); err != nil { // This is N
|
|
142
147
|
return fmt.Errorf("failed to write range loop integer expression: %w", err)
|
|
143
148
|
}
|
|
144
|
-
c.tsw.WriteLiterallyf("; %s++)
|
|
145
|
-
c.tsw.Indent(1)
|
|
146
|
-
c.tsw.WriteLine("")
|
|
147
|
-
|
|
148
|
-
// The value variable is not allowed ranging over an integer.
|
|
149
|
-
if exp.Value != nil {
|
|
150
|
-
return errors.Errorf("ranging over an integer supports key variable only (not value variable): %v", exp)
|
|
151
|
-
}
|
|
149
|
+
c.tsw.WriteLiterallyf("; %s++) ", indexVarName)
|
|
152
150
|
|
|
151
|
+
// write body
|
|
153
152
|
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
154
153
|
return fmt.Errorf("failed to write range loop integer body: %w", err)
|
|
155
154
|
}
|
|
156
|
-
|
|
157
|
-
c.tsw.WriteLine("}")
|
|
155
|
+
|
|
158
156
|
return nil
|
|
159
157
|
}
|
|
160
158
|
}
|
|
@@ -164,7 +162,7 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
|
|
|
164
162
|
_, isArray := underlying.(*types.Array)
|
|
165
163
|
if isArray || isSlice {
|
|
166
164
|
// Determine the index variable name for the generated loop
|
|
167
|
-
indexVarName := "
|
|
165
|
+
indexVarName := "_i" // Default name
|
|
168
166
|
if exp.Key != nil {
|
|
169
167
|
if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
|
|
170
168
|
indexVarName = keyIdent.Name
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"go/ast"
|
|
6
|
+
"go/token"
|
|
7
|
+
|
|
8
|
+
"github.com/pkg/errors"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// WriteStmtSelect translates a Go `select` statement into an asynchronous
|
|
12
|
+
// TypeScript operation using the `$.selectStatement` runtime helper.
|
|
13
|
+
// Go's `select` provides non-deterministic choice over channel operations.
|
|
14
|
+
// This is emulated by constructing an array of `SelectCase` objects, one for
|
|
15
|
+
// each `case` in the Go `select`, and passing it to `$.selectStatement`.
|
|
16
|
+
//
|
|
17
|
+
// Each `SelectCase` object includes:
|
|
18
|
+
// - `id`: A unique identifier for the case.
|
|
19
|
+
// - `isSend`: `true` for send operations (`case ch <- val:`), `false` for receives.
|
|
20
|
+
// - `channel`: The TypeScript channel object.
|
|
21
|
+
// - `value` (for sends): The value being sent.
|
|
22
|
+
// - `onSelected: async (result) => { ... }`: A callback executed when this case
|
|
23
|
+
// is chosen. `result` contains `{ value, ok }` for receives.
|
|
24
|
+
// - Inside `onSelected`, assignments for receive operations (e.g., `v := <-ch`,
|
|
25
|
+
// `v, ok := <-ch`) are handled by declaring/assigning variables from `result.value`
|
|
26
|
+
// and `result.ok`.
|
|
27
|
+
// - The original Go case body is then translated within this callback.
|
|
28
|
+
//
|
|
29
|
+
// A `default` case in Go `select` is translated to a `SelectCase` with `id: -1`
|
|
30
|
+
// and its body in the `onSelected` handler. The `$.selectStatement` helper
|
|
31
|
+
// is informed if a default case exists.
|
|
32
|
+
// The entire `$.selectStatement(...)` call is `await`ed because channel
|
|
33
|
+
// operations are asynchronous in the TypeScript model.
|
|
34
|
+
func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
|
|
35
|
+
// This is our implementation of the select statement, which will use Promise.race
|
|
36
|
+
// to achieve the same semantics as Go's select statement.
|
|
37
|
+
|
|
38
|
+
// Variable to track whether we have a default case
|
|
39
|
+
hasDefault := false
|
|
40
|
+
|
|
41
|
+
// Start the selectStatement call and the array literal
|
|
42
|
+
c.tsw.WriteLiterally("await $.selectStatement(")
|
|
43
|
+
c.tsw.WriteLine("[") // Put bracket on new line
|
|
44
|
+
c.tsw.Indent(1)
|
|
45
|
+
|
|
46
|
+
// For each case clause, generate a SelectCase object directly into the array literal
|
|
47
|
+
for i, stmt := range exp.Body.List {
|
|
48
|
+
if commClause, ok := stmt.(*ast.CommClause); ok {
|
|
49
|
+
if commClause.Comm == nil {
|
|
50
|
+
// This is a default case
|
|
51
|
+
hasDefault = true
|
|
52
|
+
// Add a SelectCase object for the default case with a special ID
|
|
53
|
+
c.tsw.WriteLiterally("{") // Start object literal
|
|
54
|
+
c.tsw.Indent(1)
|
|
55
|
+
c.tsw.WriteLine("")
|
|
56
|
+
c.tsw.WriteLiterally("id: -1,") // Special ID for default case
|
|
57
|
+
c.tsw.WriteLine("")
|
|
58
|
+
c.tsw.WriteLiterally("isSend: false,") // Default case is neither send nor receive, but needs a value
|
|
59
|
+
c.tsw.WriteLine("")
|
|
60
|
+
c.tsw.WriteLiterally("channel: null,") // No channel for default case
|
|
61
|
+
c.tsw.WriteLine("")
|
|
62
|
+
c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
|
|
63
|
+
c.tsw.Indent(1)
|
|
64
|
+
c.tsw.WriteLine("")
|
|
65
|
+
// Write the case body
|
|
66
|
+
for _, bodyStmt := range commClause.Body {
|
|
67
|
+
if err := c.WriteStmt(bodyStmt); err != nil {
|
|
68
|
+
return fmt.Errorf("failed to write statement in select default case body (onSelected): %w", err)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
c.tsw.Indent(-1)
|
|
72
|
+
c.tsw.WriteLine("}") // Close onSelected handler
|
|
73
|
+
c.tsw.Indent(-1)
|
|
74
|
+
c.tsw.WriteLiterally("},") // Close SelectCase object and add comma
|
|
75
|
+
c.tsw.WriteLine("")
|
|
76
|
+
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generate a unique ID for this case
|
|
81
|
+
caseID := i
|
|
82
|
+
|
|
83
|
+
// Start writing the SelectCase object
|
|
84
|
+
c.tsw.WriteLiterally("{") // Start object literal
|
|
85
|
+
c.tsw.Indent(1)
|
|
86
|
+
c.tsw.WriteLine("")
|
|
87
|
+
c.tsw.WriteLiterallyf("id: %d,", caseID)
|
|
88
|
+
c.tsw.WriteLine("")
|
|
89
|
+
|
|
90
|
+
// Handle different types of comm statements
|
|
91
|
+
switch comm := commClause.Comm.(type) {
|
|
92
|
+
case *ast.AssignStmt:
|
|
93
|
+
// This is a receive operation with assignment: case v := <-ch: or case v, ok := <-ch:
|
|
94
|
+
if len(comm.Rhs) == 1 {
|
|
95
|
+
if unaryExpr, ok := comm.Rhs[0].(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
96
|
+
// It's a receive operation
|
|
97
|
+
c.tsw.WriteLiterally("isSend: false,")
|
|
98
|
+
c.tsw.WriteLine("")
|
|
99
|
+
c.tsw.WriteLiterally("channel: ")
|
|
100
|
+
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
|
|
101
|
+
return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
|
|
102
|
+
}
|
|
103
|
+
c.tsw.WriteLiterally(",")
|
|
104
|
+
c.tsw.WriteLine("")
|
|
105
|
+
} else {
|
|
106
|
+
c.tsw.WriteCommentLinef("unhandled RHS in select assignment case: %T", comm.Rhs[0])
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
c.tsw.WriteCommentLinef("unhandled RHS count in select assignment case: %d", len(comm.Rhs))
|
|
110
|
+
}
|
|
111
|
+
case *ast.ExprStmt:
|
|
112
|
+
// This is a simple receive: case <-ch:
|
|
113
|
+
if unaryExpr, ok := comm.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
114
|
+
c.tsw.WriteLiterally("isSend: false,")
|
|
115
|
+
c.tsw.WriteLine("")
|
|
116
|
+
c.tsw.WriteLiterally("channel: ")
|
|
117
|
+
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
|
|
118
|
+
return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
|
|
119
|
+
}
|
|
120
|
+
c.tsw.WriteLiterally(",")
|
|
121
|
+
c.tsw.WriteLine("")
|
|
122
|
+
} else {
|
|
123
|
+
c.tsw.WriteCommentLinef("unhandled expression in select case: %T", comm.X)
|
|
124
|
+
}
|
|
125
|
+
case *ast.SendStmt:
|
|
126
|
+
// This is a send operation: case ch <- v:
|
|
127
|
+
c.tsw.WriteLiterally("isSend: true,")
|
|
128
|
+
c.tsw.WriteLine("")
|
|
129
|
+
c.tsw.WriteLiterally("channel: ")
|
|
130
|
+
if err := c.WriteValueExpr(comm.Chan); err != nil { // The channel expression
|
|
131
|
+
return fmt.Errorf("failed to write channel expression in select send case: %w", err)
|
|
132
|
+
}
|
|
133
|
+
c.tsw.WriteLiterally(",")
|
|
134
|
+
c.tsw.WriteLine("")
|
|
135
|
+
c.tsw.WriteLiterally("value: ")
|
|
136
|
+
if err := c.WriteValueExpr(comm.Value); err != nil { // The value expression
|
|
137
|
+
return fmt.Errorf("failed to write value expression in select send case: %w", err)
|
|
138
|
+
}
|
|
139
|
+
c.tsw.WriteLiterally(",")
|
|
140
|
+
c.tsw.WriteLine("")
|
|
141
|
+
default:
|
|
142
|
+
c.tsw.WriteCommentLinef("unhandled comm statement in select case: %T", comm)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Add the onSelected handler to execute the case body after the select resolves
|
|
146
|
+
c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
|
|
147
|
+
c.tsw.Indent(1)
|
|
148
|
+
c.tsw.WriteLine("")
|
|
149
|
+
|
|
150
|
+
// Handle assignment for channel receives if needed (inside the onSelected handler)
|
|
151
|
+
if assignStmt, ok := commClause.Comm.(*ast.AssignStmt); ok {
|
|
152
|
+
// This is a receive operation with assignment
|
|
153
|
+
if len(assignStmt.Lhs) == 1 {
|
|
154
|
+
// Simple receive: case v := <-ch:
|
|
155
|
+
valIdent, ok := assignStmt.Lhs[0].(*ast.Ident)
|
|
156
|
+
if ok && valIdent.Name != "_" { // Check for blank identifier
|
|
157
|
+
c.tsw.WriteLiterally("const ")
|
|
158
|
+
c.WriteIdent(valIdent, false)
|
|
159
|
+
c.tsw.WriteLiterally(" = result.value")
|
|
160
|
+
c.tsw.WriteLine("")
|
|
161
|
+
}
|
|
162
|
+
} else if len(assignStmt.Lhs) == 2 {
|
|
163
|
+
// Receive with ok: case v, ok := <-ch:
|
|
164
|
+
valIdent, valOk := assignStmt.Lhs[0].(*ast.Ident)
|
|
165
|
+
okIdent, okOk := assignStmt.Lhs[1].(*ast.Ident)
|
|
166
|
+
|
|
167
|
+
if valOk && valIdent.Name != "_" {
|
|
168
|
+
c.tsw.WriteLiterally("const ")
|
|
169
|
+
c.WriteIdent(valIdent, false)
|
|
170
|
+
c.tsw.WriteLiterally(" = result.value")
|
|
171
|
+
c.tsw.WriteLine("")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if okOk && okIdent.Name != "_" {
|
|
175
|
+
c.tsw.WriteLiterally("const ")
|
|
176
|
+
c.WriteIdent(okIdent, false)
|
|
177
|
+
c.tsw.WriteLiterally(" = result.ok")
|
|
178
|
+
c.tsw.WriteLine("")
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Note: Simple receive (case <-ch:) and send (case ch <- v:) don't require assignment here,
|
|
183
|
+
// as the operation was already performed by selectReceive/selectSend and the result is in 'result'.
|
|
184
|
+
|
|
185
|
+
// Write the case body
|
|
186
|
+
for _, bodyStmt := range commClause.Body {
|
|
187
|
+
if err := c.WriteStmt(bodyStmt); err != nil {
|
|
188
|
+
return fmt.Errorf("failed to write statement in select case body (onSelected): %w", err)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
c.tsw.Indent(-1)
|
|
193
|
+
c.tsw.WriteLine("}") // Close onSelected handler
|
|
194
|
+
c.tsw.Indent(-1)
|
|
195
|
+
c.tsw.WriteLiterally("},") // Close SelectCase object and add comma
|
|
196
|
+
c.tsw.WriteLine("")
|
|
197
|
+
|
|
198
|
+
} else {
|
|
199
|
+
return errors.Errorf("unknown statement in select body: %T", stmt)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Close the array literal and the selectStatement call
|
|
204
|
+
c.tsw.Indent(-1)
|
|
205
|
+
c.tsw.WriteLiterally("], ")
|
|
206
|
+
c.tsw.WriteLiterallyf("%t", hasDefault)
|
|
207
|
+
c.tsw.WriteLiterally(")")
|
|
208
|
+
c.tsw.WriteLine("")
|
|
209
|
+
|
|
210
|
+
return nil
|
|
211
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"go/ast"
|
|
6
|
+
|
|
7
|
+
"github.com/pkg/errors"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// WriteStmtTypeSwitch translates a Go `type switch` statement (`ast.TypeSwitchStmt`)
|
|
11
|
+
// into its TypeScript equivalent using the `$.typeSwitch` helper.
|
|
12
|
+
func (c *GoToTSCompiler) WriteStmtTypeSwitch(stmt *ast.TypeSwitchStmt) error {
|
|
13
|
+
// Outer block for scoping Init variable
|
|
14
|
+
if stmt.Init != nil {
|
|
15
|
+
c.tsw.WriteLine("{")
|
|
16
|
+
c.tsw.Indent(1)
|
|
17
|
+
if err := c.WriteStmt(stmt.Init); err != nil {
|
|
18
|
+
return fmt.Errorf("failed to write type switch init statement: %w", err)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Extract the subject expression and case variable identifier
|
|
23
|
+
var subjectExpr ast.Expr
|
|
24
|
+
var caseVarIdent *ast.Ident // The variable in 'v := x.(type)'
|
|
25
|
+
|
|
26
|
+
switch assignNode := stmt.Assign.(type) {
|
|
27
|
+
case *ast.AssignStmt: // v := x.(type)
|
|
28
|
+
if len(assignNode.Lhs) != 1 || len(assignNode.Rhs) != 1 {
|
|
29
|
+
return errors.Errorf("TypeSwitchStmt AssignStmt: expected 1 LHS and 1 RHS, got %d and %d", len(assignNode.Lhs), len(assignNode.Rhs))
|
|
30
|
+
}
|
|
31
|
+
ident, ok := assignNode.Lhs[0].(*ast.Ident)
|
|
32
|
+
if !ok {
|
|
33
|
+
return errors.Errorf("TypeSwitchStmt AssignStmt LHS is not *ast.Ident: %T", assignNode.Lhs[0])
|
|
34
|
+
}
|
|
35
|
+
caseVarIdent = ident
|
|
36
|
+
typeAssert, ok := assignNode.Rhs[0].(*ast.TypeAssertExpr)
|
|
37
|
+
if !ok {
|
|
38
|
+
return errors.Errorf("TypeSwitchStmt AssignStmt RHS is not *ast.TypeAssertExpr: %T", assignNode.Rhs[0])
|
|
39
|
+
}
|
|
40
|
+
if typeAssert.Type != nil {
|
|
41
|
+
return errors.Errorf("TypeSwitchStmt AssignStmt TypeAssertExpr.Type is not nil")
|
|
42
|
+
}
|
|
43
|
+
subjectExpr = typeAssert.X
|
|
44
|
+
case *ast.ExprStmt: // x.(type)
|
|
45
|
+
typeAssert, ok := assignNode.X.(*ast.TypeAssertExpr)
|
|
46
|
+
if !ok {
|
|
47
|
+
return errors.Errorf("TypeSwitchStmt ExprStmt.X is not *ast.TypeAssertExpr: %T", assignNode.X)
|
|
48
|
+
}
|
|
49
|
+
if typeAssert.Type != nil {
|
|
50
|
+
return errors.Errorf("TypeSwitchStmt ExprStmt TypeAssertExpr.Type is not nil")
|
|
51
|
+
}
|
|
52
|
+
subjectExpr = typeAssert.X
|
|
53
|
+
default:
|
|
54
|
+
return errors.Errorf("unknown Assign type in TypeSwitchStmt: %T", stmt.Assign)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Build the array of case configurations for $.typeSwitch
|
|
58
|
+
c.tsw.WriteLiterally("$.typeSwitch(")
|
|
59
|
+
if err := c.WriteValueExpr(subjectExpr); err != nil {
|
|
60
|
+
c.tsw.Indent(-1)
|
|
61
|
+
c.tsw.WriteLine("} // End TypeSwitchStmt due to error in subject")
|
|
62
|
+
return fmt.Errorf("failed to write subject expression in type switch: %w", err)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// case list
|
|
66
|
+
c.tsw.WriteLiterally(", [")
|
|
67
|
+
|
|
68
|
+
stmtBodyList := stmt.Body.List
|
|
69
|
+
var defaultCaseBody []ast.Stmt
|
|
70
|
+
|
|
71
|
+
for i, caseClauseStmt := range stmtBodyList {
|
|
72
|
+
caseClause, ok := caseClauseStmt.(*ast.CaseClause)
|
|
73
|
+
if !ok {
|
|
74
|
+
return errors.Errorf("unexpected statement in TypeSwitchStmt Body: not *ast.CaseClause but %T", caseClauseStmt)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if len(caseClause.List) == 0 { // Default case
|
|
78
|
+
defaultCaseBody = caseClause.Body
|
|
79
|
+
continue // Process default case after type cases
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Type case(s)
|
|
83
|
+
if i != 0 {
|
|
84
|
+
c.tsw.WriteLiterally(",")
|
|
85
|
+
c.tsw.WriteLine("")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
c.tsw.WriteLiterally("{ types: [")
|
|
89
|
+
for j, typeExpr := range caseClause.List {
|
|
90
|
+
if j > 0 {
|
|
91
|
+
c.tsw.WriteLiterally(", ")
|
|
92
|
+
}
|
|
93
|
+
c.writeTypeDescription(typeExpr) // Descriptor for $.is or $.typeAssert
|
|
94
|
+
}
|
|
95
|
+
c.tsw.WriteLiterally("], body: (")
|
|
96
|
+
|
|
97
|
+
// Add case variable if it exists and is not '_'
|
|
98
|
+
if caseVarIdent != nil && caseVarIdent.Name != "_" {
|
|
99
|
+
c.WriteIdent(caseVarIdent, false) // isDeclaration = false for the parameter
|
|
100
|
+
// Note: TypeScript type inference should handle the parameter type based on the case type(s)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
c.tsw.WriteLiterally(") => {")
|
|
104
|
+
|
|
105
|
+
caseClauseBody := caseClause.Body
|
|
106
|
+
if len(caseClauseBody) != 0 {
|
|
107
|
+
c.tsw.Indent(1)
|
|
108
|
+
c.tsw.WriteLine("")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for _, bodyStmt := range caseClauseBody {
|
|
112
|
+
if err := c.WriteStmt(bodyStmt); err != nil {
|
|
113
|
+
return fmt.Errorf("failed to write statement in type switch case body: %w", err)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if len(caseClauseBody) != 0 {
|
|
118
|
+
c.tsw.Indent(-1)
|
|
119
|
+
}
|
|
120
|
+
c.tsw.WriteLiterally("}") // Close case body function
|
|
121
|
+
c.tsw.WriteLiterally("}") // Close case object
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
c.tsw.WriteLiterally("]") // Close cases array
|
|
125
|
+
|
|
126
|
+
// Add default case function if it exists
|
|
127
|
+
if len(defaultCaseBody) != 0 {
|
|
128
|
+
c.tsw.WriteLiterally(", () => {")
|
|
129
|
+
c.tsw.Indent(1)
|
|
130
|
+
c.tsw.WriteLine("")
|
|
131
|
+
for _, bodyStmt := range defaultCaseBody {
|
|
132
|
+
if err := c.WriteStmt(bodyStmt); err != nil {
|
|
133
|
+
return fmt.Errorf("failed to write statement in type switch default case body: %w", err)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
c.tsw.Indent(-1)
|
|
137
|
+
c.tsw.WriteLiterally("}") // Close default case function
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
c.tsw.WriteLine(")") // Close $.typeSwitch call
|
|
141
|
+
if stmt.Init != nil {
|
|
142
|
+
c.tsw.Indent(-1)
|
|
143
|
+
c.tsw.WriteLine("}") // Close outer block
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return nil
|
|
147
|
+
}
|