goscript 0.0.21 → 0.0.22
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 +298 -33
- package/compiler/assignment.go +407 -0
- package/compiler/compiler.go +160 -5883
- package/compiler/compiler_test.go +4 -0
- package/compiler/composite-lit.go +537 -0
- package/compiler/config.go +3 -0
- package/compiler/decl.go +223 -0
- package/compiler/expr-call.go +423 -0
- package/compiler/expr-selector.go +105 -0
- package/compiler/expr-star.go +90 -0
- package/compiler/expr-type.go +182 -0
- package/compiler/expr-value.go +119 -0
- package/compiler/expr.go +356 -0
- package/compiler/field.go +169 -0
- package/compiler/lit.go +99 -0
- package/compiler/primitive.go +142 -0
- package/compiler/{write-type-spec.go → spec-struct.go} +79 -203
- package/compiler/spec-value.go +194 -0
- package/compiler/spec.go +263 -0
- package/compiler/stmt-assign.go +411 -0
- package/compiler/stmt-for.go +178 -0
- package/compiler/stmt-range.go +237 -0
- package/compiler/stmt.go +907 -0
- package/compiler/type-assert.go +463 -0
- package/compiler/type-info.go +141 -0
- package/compiler/type.go +556 -0
- package/dist/builtin/builtin.d.ts +24 -6
- package/dist/builtin/builtin.js +215 -19
- package/dist/builtin/builtin.js.map +1 -1
- package/go.mod +2 -1
- package/go.sum +4 -2
- package/package.json +2 -2
- /package/compiler/{writer.go → code-writer.go} +0 -0
package/compiler/spec.go
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"go/ast"
|
|
6
|
+
"go/types"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
// types provides type information for Go types.
|
|
10
|
+
"github.com/pkg/errors"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// WriteSpec is a dispatcher function that translates a Go specification node
|
|
14
|
+
// (`ast.Spec`) into its TypeScript equivalent. It handles different types of
|
|
15
|
+
// specifications found within `GenDecl` (general declarations):
|
|
16
|
+
// - `ast.ImportSpec` (import declarations): Delegates to `WriteImportSpec`.
|
|
17
|
+
// - `ast.ValueSpec` (variable or constant declarations): Delegates to `WriteValueSpec`.
|
|
18
|
+
// - `ast.TypeSpec` (type definitions like structs, interfaces): Delegates to `WriteTypeSpec`.
|
|
19
|
+
// If an unknown specification type is encountered, it returns an error.
|
|
20
|
+
func (c *GoToTSCompiler) WriteSpec(a ast.Spec) error {
|
|
21
|
+
switch d := a.(type) {
|
|
22
|
+
case *ast.ImportSpec:
|
|
23
|
+
c.WriteImportSpec(d)
|
|
24
|
+
case *ast.ValueSpec:
|
|
25
|
+
if err := c.WriteValueSpec(d); err != nil {
|
|
26
|
+
return err
|
|
27
|
+
}
|
|
28
|
+
case *ast.TypeSpec:
|
|
29
|
+
if err := c.WriteTypeSpec(d); err != nil {
|
|
30
|
+
return err
|
|
31
|
+
}
|
|
32
|
+
default:
|
|
33
|
+
return fmt.Errorf("unknown spec type: %T", a)
|
|
34
|
+
}
|
|
35
|
+
return nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func (c *GoToTSCompiler) getEmbeddedFieldKeyName(fieldType types.Type) string {
|
|
39
|
+
trueType := fieldType
|
|
40
|
+
if ptr, isPtr := trueType.(*types.Pointer); isPtr {
|
|
41
|
+
trueType = ptr.Elem()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if named, isNamed := trueType.(*types.Named); isNamed {
|
|
45
|
+
return named.Obj().Name()
|
|
46
|
+
} else {
|
|
47
|
+
// Fallback for unnamed embedded types, though less common for structs
|
|
48
|
+
fieldKeyName := strings.Title(trueType.String()) // Simple heuristic
|
|
49
|
+
if dotIndex := strings.LastIndex(fieldKeyName, "."); dotIndex != -1 {
|
|
50
|
+
fieldKeyName = fieldKeyName[dotIndex+1:]
|
|
51
|
+
}
|
|
52
|
+
return fieldKeyName
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Type, doc, comment *ast.CommentGroup) {
|
|
57
|
+
fieldTypeStr := c.getTypeString(fieldType)
|
|
58
|
+
|
|
59
|
+
// Generate getter
|
|
60
|
+
if doc != nil {
|
|
61
|
+
c.WriteDoc(doc)
|
|
62
|
+
}
|
|
63
|
+
if comment != nil {
|
|
64
|
+
c.WriteDoc(comment)
|
|
65
|
+
}
|
|
66
|
+
c.tsw.WriteLinef("public get %s(): %s {", fieldName, fieldTypeStr)
|
|
67
|
+
c.tsw.Indent(1)
|
|
68
|
+
c.tsw.WriteLinef("return this._fields.%s.value", fieldName)
|
|
69
|
+
c.tsw.Indent(-1)
|
|
70
|
+
c.tsw.WriteLine("}")
|
|
71
|
+
|
|
72
|
+
// Generate setter (no comments)
|
|
73
|
+
c.tsw.WriteLinef("public set %s(value: %s) {", fieldName, fieldTypeStr)
|
|
74
|
+
c.tsw.Indent(1)
|
|
75
|
+
c.tsw.WriteLinef("this._fields.%s.value = value", fieldName)
|
|
76
|
+
c.tsw.Indent(-1)
|
|
77
|
+
c.tsw.WriteLine("}")
|
|
78
|
+
c.tsw.WriteLine("")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func (c *GoToTSCompiler) writeBoxedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
|
|
82
|
+
c.tsw.WriteLiterally(fieldName)
|
|
83
|
+
c.tsw.WriteLiterally(": $.box(")
|
|
84
|
+
|
|
85
|
+
if isEmbedded {
|
|
86
|
+
if _, isPtr := fieldType.(*types.Pointer); isPtr {
|
|
87
|
+
c.tsw.WriteLiterallyf("init?.%s ?? null", fieldName)
|
|
88
|
+
} else {
|
|
89
|
+
typeForNew := fieldName
|
|
90
|
+
c.tsw.WriteLiterallyf("new %s(init?.%s)", typeForNew, fieldName)
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
isStructValueType := false
|
|
94
|
+
var structTypeNameForClone string
|
|
95
|
+
if named, ok := fieldType.(*types.Named); ok {
|
|
96
|
+
if _, isStruct := named.Underlying().(*types.Struct); isStruct {
|
|
97
|
+
isStructValueType = true
|
|
98
|
+
structTypeNameForClone = named.Obj().Name()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if isStructValueType {
|
|
103
|
+
c.tsw.WriteLiterallyf("init?.%s?.clone() ?? new %s()", fieldName, structTypeNameForClone)
|
|
104
|
+
} else {
|
|
105
|
+
c.tsw.WriteLiterallyf("init?.%s ?? ", fieldName)
|
|
106
|
+
c.WriteZeroValueForType(fieldType)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
c.tsw.WriteLiterally(")")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func (c *GoToTSCompiler) writeClonedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
|
|
114
|
+
c.tsw.WriteLiterally(fieldName)
|
|
115
|
+
c.tsw.WriteLiterally(": $.box(")
|
|
116
|
+
|
|
117
|
+
if isEmbedded {
|
|
118
|
+
isPointerToStruct := false
|
|
119
|
+
trueType := fieldType
|
|
120
|
+
if ptr, isPtr := trueType.(*types.Pointer); isPtr {
|
|
121
|
+
trueType = ptr.Elem()
|
|
122
|
+
isPointerToStruct = true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if named, isNamed := trueType.(*types.Named); isNamed {
|
|
126
|
+
_, isUnderlyingStruct := named.Underlying().(*types.Struct)
|
|
127
|
+
if isUnderlyingStruct && !isPointerToStruct { // Is a value struct
|
|
128
|
+
c.tsw.WriteLiterallyf("this._fields.%s.value.clone()", fieldName)
|
|
129
|
+
} else { // Is a pointer to a struct, or not a struct
|
|
130
|
+
c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
isValueTypeStruct := false
|
|
137
|
+
if named, ok := fieldType.(*types.Named); ok {
|
|
138
|
+
if _, isStruct := named.Underlying().(*types.Struct); isStruct {
|
|
139
|
+
isValueTypeStruct = true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if isValueTypeStruct {
|
|
144
|
+
c.tsw.WriteLiterallyf("this._fields.%s.value?.clone() ?? null", fieldName)
|
|
145
|
+
} else {
|
|
146
|
+
c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
c.tsw.WriteLiterally(")")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// WriteTypeSpec writes the type specification to the output.
|
|
154
|
+
func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
|
|
155
|
+
if a.Doc != nil {
|
|
156
|
+
c.WriteDoc(a.Doc)
|
|
157
|
+
}
|
|
158
|
+
if a.Comment != nil {
|
|
159
|
+
c.WriteDoc(a.Comment)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
switch t := a.Type.(type) {
|
|
163
|
+
case *ast.StructType:
|
|
164
|
+
return c.WriteStructTypeSpec(a, t)
|
|
165
|
+
case *ast.InterfaceType:
|
|
166
|
+
return c.WriteInterfaceTypeSpec(a, t)
|
|
167
|
+
default:
|
|
168
|
+
// type alias
|
|
169
|
+
c.tsw.WriteLiterally("type ")
|
|
170
|
+
if err := c.WriteValueExpr(a.Name); err != nil {
|
|
171
|
+
return err
|
|
172
|
+
}
|
|
173
|
+
c.tsw.WriteLiterally(" = ")
|
|
174
|
+
c.WriteTypeExpr(a.Type) // The aliased type
|
|
175
|
+
c.tsw.WriteLine(";")
|
|
176
|
+
}
|
|
177
|
+
return nil
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// WriteInterfaceTypeSpec writes the TypeScript type for a Go interface type.
|
|
181
|
+
func (c *GoToTSCompiler) WriteInterfaceTypeSpec(a *ast.TypeSpec, t *ast.InterfaceType) error {
|
|
182
|
+
c.tsw.WriteLiterally("type ")
|
|
183
|
+
if err := c.WriteValueExpr(a.Name); err != nil {
|
|
184
|
+
return err
|
|
185
|
+
}
|
|
186
|
+
c.tsw.WriteLiterally(" = ")
|
|
187
|
+
// Get the types.Interface from the ast.InterfaceType.
|
|
188
|
+
// For an interface definition like `type MyInterface interface { M() }`,
|
|
189
|
+
// 't' is the *ast.InterfaceType representing `interface { M() }`.
|
|
190
|
+
// TypesInfo.TypeOf(t) will give the *types.Interface.
|
|
191
|
+
goType := c.pkg.TypesInfo.TypeOf(t)
|
|
192
|
+
if goType == nil {
|
|
193
|
+
return errors.Errorf("could not get type for interface AST node for %s", a.Name.Name)
|
|
194
|
+
}
|
|
195
|
+
ifaceType, ok := goType.(*types.Interface)
|
|
196
|
+
if !ok {
|
|
197
|
+
return errors.Errorf("expected *types.Interface, got %T for %s when processing interface literal", goType, a.Name.Name)
|
|
198
|
+
}
|
|
199
|
+
c.WriteInterfaceType(ifaceType, t) // Pass the *ast.InterfaceType for comment fetching
|
|
200
|
+
c.tsw.WriteLine("")
|
|
201
|
+
|
|
202
|
+
// Add code to register the interface with the runtime system
|
|
203
|
+
interfaceName := a.Name.Name
|
|
204
|
+
c.tsw.WriteLine("")
|
|
205
|
+
c.tsw.WriteLinef("$.registerInterfaceType(")
|
|
206
|
+
c.tsw.WriteLinef(" '%s',", interfaceName)
|
|
207
|
+
c.tsw.WriteLinef(" null, // Zero value for interface is null")
|
|
208
|
+
|
|
209
|
+
// Collect methods for the interface type
|
|
210
|
+
var interfaceMethods []*types.Func
|
|
211
|
+
if ifaceType != nil { // ifaceType is *types.Interface
|
|
212
|
+
for i := range ifaceType.NumExplicitMethods() {
|
|
213
|
+
interfaceMethods = append(interfaceMethods, ifaceType.ExplicitMethod(i))
|
|
214
|
+
}
|
|
215
|
+
// TODO: Handle embedded interface methods if necessary for full signature collection.
|
|
216
|
+
// For now, explicit methods are covered.
|
|
217
|
+
}
|
|
218
|
+
c.tsw.WriteLiterally(" [")
|
|
219
|
+
c.writeMethodSignatures(interfaceMethods)
|
|
220
|
+
c.tsw.WriteLiterally("]")
|
|
221
|
+
c.tsw.WriteLine("")
|
|
222
|
+
|
|
223
|
+
c.tsw.WriteLinef(");")
|
|
224
|
+
|
|
225
|
+
return nil
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// WriteImportSpec translates a Go import specification (`ast.ImportSpec`)
|
|
229
|
+
// into a TypeScript import statement.
|
|
230
|
+
//
|
|
231
|
+
// It extracts the Go import path (e.g., `"path/to/pkg"`) and determines the
|
|
232
|
+
// import alias/name for TypeScript. If the Go import has an explicit name
|
|
233
|
+
// (e.g., `alias "path/to/pkg"`), that alias is used. Otherwise, the package
|
|
234
|
+
// name is derived from the Go path.
|
|
235
|
+
//
|
|
236
|
+
// The Go path is then translated to a TypeScript module path using
|
|
237
|
+
// `translateGoPathToTypescriptPath`.
|
|
238
|
+
//
|
|
239
|
+
// Finally, it writes a TypeScript import statement like `import * as alias from "typescript/path/to/pkg";`
|
|
240
|
+
// and records the import details in `c.analysis.Imports` for later use (e.g.,
|
|
241
|
+
// resolving qualified identifiers).
|
|
242
|
+
func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
243
|
+
if a.Doc != nil {
|
|
244
|
+
c.WriteDoc(a.Doc)
|
|
245
|
+
}
|
|
246
|
+
if a.Comment != nil {
|
|
247
|
+
c.WriteDoc(a.Comment)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
goPath := a.Path.Value[1 : len(a.Path.Value)-1]
|
|
251
|
+
impName := packageNameFromGoPath(goPath)
|
|
252
|
+
if a.Name != nil && a.Name.Name != "" {
|
|
253
|
+
impName = a.Name.Name
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
importPath := translateGoPathToTypescriptPath(goPath)
|
|
257
|
+
c.analysis.Imports[impName] = &fileImport{
|
|
258
|
+
importPath: importPath,
|
|
259
|
+
importVars: make(map[string]struct{}),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
c.tsw.WriteImport(impName, importPath+"/index.js")
|
|
263
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"go/ast"
|
|
6
|
+
"go/token"
|
|
7
|
+
"go/types"
|
|
8
|
+
|
|
9
|
+
"github.com/pkg/errors"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// WriteStmtAssign translates a Go assignment statement (`ast.AssignStmt`) into
|
|
13
|
+
// its TypeScript equivalent. It handles various forms of Go assignments:
|
|
14
|
+
//
|
|
15
|
+
// 1. **Multi-variable assignment from a single function call** (e.g., `a, b := fn()`):
|
|
16
|
+
// - Uses `writeMultiVarAssignFromCall` to generate `let [a, b] = fn_ts();`.
|
|
17
|
+
//
|
|
18
|
+
// 2. **Type assertion with comma-ok** (e.g., `val, ok := expr.(Type)`):
|
|
19
|
+
// - Uses `writeTypeAssertion` to generate `let { value: val, ok: ok } = $.typeAssert<Type_ts>(expr_ts, 'TypeName');`.
|
|
20
|
+
//
|
|
21
|
+
// 3. **Map lookup with comma-ok** (e.g., `val, ok := myMap[key]`):
|
|
22
|
+
// - Uses `writeMapLookupWithExists` to generate separate assignments for `val`
|
|
23
|
+
// (using `myMap_ts.get(key_ts) ?? zeroValue`) and `ok` (using `myMap_ts.has(key_ts)`).
|
|
24
|
+
//
|
|
25
|
+
// 4. **Channel receive with comma-ok** (e.g., `val, ok := <-ch`):
|
|
26
|
+
// - Uses `writeChannelReceiveWithOk` to generate `let { value: val, ok: ok } = await ch_ts.receiveWithOk();`.
|
|
27
|
+
//
|
|
28
|
+
// 5. **Discarded channel receive** (e.g., `<-ch` on RHS, no LHS vars):
|
|
29
|
+
// - Translates to `await ch_ts.receive();`.
|
|
30
|
+
//
|
|
31
|
+
// 6. **Single assignment** (e.g., `x = y`, `x := y`, `*p = y`, `x[i] = y`):
|
|
32
|
+
// - Uses `writeAssignmentCore` which handles:
|
|
33
|
+
// - Blank identifier `_` on LHS (evaluates RHS for side effects).
|
|
34
|
+
// - Assignment to dereferenced pointer `*p = val` -> `p_ts!.value = val_ts`.
|
|
35
|
+
// - Short declaration `x := y`: `let x = y_ts;`. If `x` is boxed, `let x: $.Box<T> = $.box(y_ts);`.
|
|
36
|
+
// - Regular assignment `x = y`, including compound assignments like `x += y`.
|
|
37
|
+
// - Assignment to map index `m[k] = v` using `$.mapSet`.
|
|
38
|
+
// - Struct value assignment `s1 = s2` becomes `s1 = s2.clone()` if `s2` is a struct.
|
|
39
|
+
//
|
|
40
|
+
// 7. **Multi-variable assignment with multiple RHS values** (e.g., `a, b = x, y`):
|
|
41
|
+
// - Uses `writeAssignmentCore` to generate `[a,b] = [x_ts, y_ts];` (or `let [a,b] = ...` for `:=`).
|
|
42
|
+
//
|
|
43
|
+
// The function ensures that the number of LHS and RHS expressions matches for
|
|
44
|
+
// most cases, erroring if they don't, except for specifically handled patterns
|
|
45
|
+
// like multi-assign from single call or discarded channel receive.
|
|
46
|
+
// It correctly applies `let` for `:=` (define) tokens and handles boxing and
|
|
47
|
+
// cloning semantics based on type information and analysis.
|
|
48
|
+
func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
49
|
+
// writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
|
|
50
|
+
writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
|
|
51
|
+
// For token.DEFINE (:=), we need to check if any of the variables are already declared
|
|
52
|
+
// In Go, := can be used for redeclaration if at least one variable is new
|
|
53
|
+
if tok == token.DEFINE {
|
|
54
|
+
// For token.DEFINE (:=), we need to handle variable declarations differently
|
|
55
|
+
// In Go, := can redeclare existing variables if at least one variable is new
|
|
56
|
+
|
|
57
|
+
// First, identify which variables are new vs existing
|
|
58
|
+
newVars := make([]bool, len(lhs))
|
|
59
|
+
anyNewVars := false
|
|
60
|
+
allNewVars := true
|
|
61
|
+
|
|
62
|
+
// For multi-variable assignments with :=, we need to determine which variables
|
|
63
|
+
// are already in scope and which are new declarations
|
|
64
|
+
for i, lhsExpr := range lhs {
|
|
65
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
|
|
66
|
+
// In Go, variables declared with := can be redeclared if at least one is new
|
|
67
|
+
// For TypeScript, we need to separately declare new variables
|
|
68
|
+
|
|
69
|
+
// Check if this variable is already in scope
|
|
70
|
+
// - If the variable is used elsewhere before this point, it's existing
|
|
71
|
+
// - Otherwise, it's a new variable being declared
|
|
72
|
+
isNew := true
|
|
73
|
+
|
|
74
|
+
// Check if the variable is used elsewhere in the code
|
|
75
|
+
if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
76
|
+
// If it's in Uses, it's referenced elsewhere, so it exists
|
|
77
|
+
isNew = false
|
|
78
|
+
allNewVars = false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
newVars[i] = isNew
|
|
82
|
+
if isNew {
|
|
83
|
+
anyNewVars = true
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get function return types if available
|
|
89
|
+
var resultTypes []*types.Var
|
|
90
|
+
if callExpr.Fun != nil {
|
|
91
|
+
if funcType, ok := c.pkg.TypesInfo.TypeOf(callExpr.Fun).Underlying().(*types.Signature); ok {
|
|
92
|
+
if funcType.Results() != nil && funcType.Results().Len() > 0 {
|
|
93
|
+
for i := 0; i < funcType.Results().Len(); i++ {
|
|
94
|
+
resultTypes = append(resultTypes, funcType.Results().At(i))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if allNewVars && anyNewVars {
|
|
101
|
+
c.tsw.WriteLiterally("let [")
|
|
102
|
+
|
|
103
|
+
for i, lhsExpr := range lhs {
|
|
104
|
+
if i != 0 {
|
|
105
|
+
c.tsw.WriteLiterally(", ")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
109
|
+
if ident.Name == "_" {
|
|
110
|
+
// For underscore variables, use empty slots in destructuring pattern
|
|
111
|
+
} else {
|
|
112
|
+
c.WriteIdent(ident, false)
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
c.WriteValueExpr(lhsExpr)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
c.tsw.WriteLiterally("] = ")
|
|
119
|
+
c.WriteValueExpr(callExpr)
|
|
120
|
+
c.tsw.WriteLine("")
|
|
121
|
+
return nil
|
|
122
|
+
} else if anyNewVars {
|
|
123
|
+
// If only some variables are new, declare them separately before the assignment
|
|
124
|
+
// Declare each new variable with appropriate type
|
|
125
|
+
for i, lhsExpr := range lhs {
|
|
126
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && newVars[i] {
|
|
127
|
+
c.tsw.WriteLiterally("let ")
|
|
128
|
+
c.WriteIdent(ident, false)
|
|
129
|
+
|
|
130
|
+
// Add type annotation if we have type information
|
|
131
|
+
if i < len(resultTypes) {
|
|
132
|
+
c.tsw.WriteLiterally(": ")
|
|
133
|
+
c.WriteGoType(resultTypes[i].Type())
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
c.tsw.WriteLine("")
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// First, collect all the selector expressions to identify variables that need to be initialized
|
|
143
|
+
hasSelectors := false
|
|
144
|
+
for _, lhsExpr := range lhs {
|
|
145
|
+
if _, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
146
|
+
hasSelectors = true
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If we have selector expressions, we need to ensure variables are initialized
|
|
152
|
+
// before the destructuring assignment
|
|
153
|
+
if hasSelectors {
|
|
154
|
+
c.tsw.WriteLiterally("{")
|
|
155
|
+
c.tsw.WriteLine("")
|
|
156
|
+
|
|
157
|
+
// Write a temporary variable to hold the function call result
|
|
158
|
+
c.tsw.WriteLiterally(" const _tmp = ")
|
|
159
|
+
if err := c.WriteValueExpr(callExpr); err != nil {
|
|
160
|
+
return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
|
|
161
|
+
}
|
|
162
|
+
c.tsw.WriteLine("")
|
|
163
|
+
|
|
164
|
+
for i, lhsExpr := range lhs {
|
|
165
|
+
// Skip underscore variables
|
|
166
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Write the LHS with indentation
|
|
171
|
+
c.tsw.WriteLiterally(" ")
|
|
172
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
173
|
+
c.WriteIdent(ident, false)
|
|
174
|
+
} else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
175
|
+
if err := c.WriteValueExpr(selectorExpr); err != nil {
|
|
176
|
+
return fmt.Errorf("failed to write selector expression in LHS: %w", err)
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Write the assignment
|
|
183
|
+
c.tsw.WriteLiterallyf(" = _tmp[%d]", i)
|
|
184
|
+
// Always add a newline after each assignment
|
|
185
|
+
c.tsw.WriteLine("")
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Close the block scope
|
|
189
|
+
c.tsw.WriteLiterally("}")
|
|
190
|
+
c.tsw.WriteLine("")
|
|
191
|
+
|
|
192
|
+
return nil
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// For simple cases without selector expressions, use array destructuring
|
|
196
|
+
c.tsw.WriteLiterally("[")
|
|
197
|
+
|
|
198
|
+
for i, lhsExpr := range lhs {
|
|
199
|
+
if i != 0 {
|
|
200
|
+
c.tsw.WriteLiterally(", ")
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
204
|
+
// For underscore variables, use empty slots in destructuring pattern
|
|
205
|
+
if ident.Name != "_" {
|
|
206
|
+
c.WriteIdent(ident, false)
|
|
207
|
+
}
|
|
208
|
+
} else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
209
|
+
// Handle selector expressions (e.g., a.b) by using WriteValueExpr
|
|
210
|
+
if err := c.WriteValueExpr(selectorExpr); err != nil {
|
|
211
|
+
return fmt.Errorf("failed to write selector expression in LHS: %w", err)
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// Should not happen for valid Go code in this context, but handle defensively
|
|
215
|
+
return errors.Errorf("unhandled LHS expression in destructuring: %T", lhsExpr)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
c.tsw.WriteLiterally("] = ")
|
|
219
|
+
|
|
220
|
+
// Write the right-hand side (the function call)
|
|
221
|
+
if err := c.WriteValueExpr(callExpr); err != nil {
|
|
222
|
+
return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
c.tsw.WriteLine("")
|
|
226
|
+
return nil
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
|
|
230
|
+
// Note: We don't use WriteIndexExpr here because we need to handle .has() and .get() separately
|
|
231
|
+
writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
|
|
232
|
+
// First check that we have exactly two LHS expressions (value and exists)
|
|
233
|
+
if len(lhs) != 2 {
|
|
234
|
+
return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check for blank identifiers and get variable names
|
|
238
|
+
valueIsBlank := false
|
|
239
|
+
existsIsBlank := false
|
|
240
|
+
var valueName string
|
|
241
|
+
var existsName string
|
|
242
|
+
|
|
243
|
+
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
244
|
+
if valIdent.Name == "_" {
|
|
245
|
+
valueIsBlank = true
|
|
246
|
+
} else {
|
|
247
|
+
valueName = valIdent.Name
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if existsIdent, ok := lhs[1].(*ast.Ident); ok {
|
|
254
|
+
if existsIdent.Name == "_" {
|
|
255
|
+
existsIsBlank = true
|
|
256
|
+
} else {
|
|
257
|
+
existsName = existsIdent.Name
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
return fmt.Errorf("unhandled LHS expression type for exists in map comma-ok: %T", lhs[1])
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Declare variables if using := and not blank
|
|
264
|
+
if tok == token.DEFINE {
|
|
265
|
+
if !valueIsBlank {
|
|
266
|
+
c.tsw.WriteLiterally("let ")
|
|
267
|
+
c.tsw.WriteLiterally(valueName)
|
|
268
|
+
// TODO: Add type annotation based on map value type
|
|
269
|
+
c.tsw.WriteLine("")
|
|
270
|
+
}
|
|
271
|
+
if !existsIsBlank {
|
|
272
|
+
c.tsw.WriteLiterally("let ")
|
|
273
|
+
c.tsw.WriteLiterally(existsName)
|
|
274
|
+
c.tsw.WriteLiterally(": boolean") // exists is always boolean
|
|
275
|
+
c.tsw.WriteLine("")
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Assign 'exists'
|
|
280
|
+
if !existsIsBlank {
|
|
281
|
+
c.tsw.WriteLiterally(existsName)
|
|
282
|
+
c.tsw.WriteLiterally(" = ")
|
|
283
|
+
c.tsw.WriteLiterally("$.mapHas(")
|
|
284
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
285
|
+
return err
|
|
286
|
+
}
|
|
287
|
+
c.tsw.WriteLiterally(", ")
|
|
288
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
289
|
+
return err
|
|
290
|
+
}
|
|
291
|
+
c.tsw.WriteLiterally(")")
|
|
292
|
+
c.tsw.WriteLine("")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Assign 'value'
|
|
296
|
+
if !valueIsBlank {
|
|
297
|
+
c.tsw.WriteLiterally(valueName)
|
|
298
|
+
c.tsw.WriteLiterally(" = ")
|
|
299
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
300
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
301
|
+
return err
|
|
302
|
+
}
|
|
303
|
+
c.tsw.WriteLiterally(", ")
|
|
304
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
305
|
+
return err
|
|
306
|
+
}
|
|
307
|
+
c.tsw.WriteLiterally(", ")
|
|
308
|
+
// Write the zero value for the map's value type
|
|
309
|
+
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
310
|
+
if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
311
|
+
c.WriteZeroValueForType(mapType.Elem())
|
|
312
|
+
} else {
|
|
313
|
+
// Fallback zero value if type info is missing or not a map
|
|
314
|
+
c.tsw.WriteLiterally("null")
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
c.tsw.WriteLiterally("null")
|
|
318
|
+
}
|
|
319
|
+
c.tsw.WriteLiterally(")")
|
|
320
|
+
c.tsw.WriteLine("")
|
|
321
|
+
} else if existsIsBlank {
|
|
322
|
+
// If both are blank, still evaluate for side effects (though .has/.get are usually pure)
|
|
323
|
+
// We add a ; otherwise TypeScript thinks we are invoking a function.
|
|
324
|
+
c.tsw.WriteLiterally(";(") // Wrap in parens to make it an expression statement
|
|
325
|
+
c.tsw.WriteLiterally("$.mapHas(")
|
|
326
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
327
|
+
return err
|
|
328
|
+
}
|
|
329
|
+
c.tsw.WriteLiterally(", ")
|
|
330
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
331
|
+
return err
|
|
332
|
+
}
|
|
333
|
+
c.tsw.WriteLiterally("), ") // Evaluate .has
|
|
334
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
335
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
336
|
+
return err
|
|
337
|
+
}
|
|
338
|
+
c.tsw.WriteLiterally(", ")
|
|
339
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
340
|
+
return err
|
|
341
|
+
}
|
|
342
|
+
c.tsw.WriteLiterally(", null))") // Evaluate .get with null as default
|
|
343
|
+
c.tsw.WriteLine("")
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return nil
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Handle multi-variable assignment from a single expression.
|
|
350
|
+
if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
|
|
351
|
+
rhsExpr := exp.Rhs[0]
|
|
352
|
+
if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
|
|
353
|
+
return c.writeTypeAssert(exp.Lhs, typeAssertExpr, exp.Tok)
|
|
354
|
+
} else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
|
|
355
|
+
// Check if this is a map lookup (comma-ok idiom)
|
|
356
|
+
if len(exp.Lhs) == 2 {
|
|
357
|
+
// Get the type of the indexed expression
|
|
358
|
+
if c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
359
|
+
tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
|
|
360
|
+
if ok {
|
|
361
|
+
// Check if it's a map type
|
|
362
|
+
if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
363
|
+
return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} else if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
369
|
+
// Handle val, ok := <-channel
|
|
370
|
+
if len(exp.Lhs) == 2 {
|
|
371
|
+
return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
|
|
372
|
+
}
|
|
373
|
+
// If LHS count is not 2, fall through to error or other handling
|
|
374
|
+
} else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
|
|
375
|
+
return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
|
|
376
|
+
}
|
|
377
|
+
// If none of the specific multi-assign patterns match, fall through to the error check below
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Ensure LHS and RHS have the same length for valid Go code in these cases
|
|
381
|
+
if len(exp.Lhs) != len(exp.Rhs) {
|
|
382
|
+
return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Handle multi-variable assignment (e.g., swaps) using writeAssignmentCore
|
|
386
|
+
if len(exp.Lhs) > 1 {
|
|
387
|
+
// Need to handle := for multi-variable declarations
|
|
388
|
+
if exp.Tok == token.DEFINE {
|
|
389
|
+
c.tsw.WriteLiterally("let ") // Use let for multi-variable declarations
|
|
390
|
+
}
|
|
391
|
+
// For multi-variable assignments, we've already added the "let" if needed
|
|
392
|
+
if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, false); err != nil {
|
|
393
|
+
return err
|
|
394
|
+
}
|
|
395
|
+
c.tsw.WriteLine("") // Add newline after the statement
|
|
396
|
+
return nil
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Handle single assignment using writeAssignmentCore
|
|
400
|
+
if len(exp.Lhs) == 1 {
|
|
401
|
+
addDeclaration := exp.Tok == token.DEFINE
|
|
402
|
+
if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
|
|
403
|
+
return err
|
|
404
|
+
}
|
|
405
|
+
c.tsw.WriteLine("") // Add newline after the statement
|
|
406
|
+
return nil
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Should not reach here if LHS/RHS counts are valid and handled
|
|
410
|
+
return fmt.Errorf("unhandled assignment case")
|
|
411
|
+
}
|