goscript 0.0.58 → 0.0.59
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/README.md +40 -33
- package/compiler/analysis.go +115 -19
- package/compiler/assignment.go +163 -217
- package/compiler/compiler.go +35 -31
- package/compiler/composite-lit.go +233 -196
- package/compiler/constraint.go +88 -0
- package/compiler/decl.go +82 -24
- package/compiler/expr-call-async.go +20 -34
- package/compiler/expr-call-builtins.go +19 -0
- package/compiler/expr-call-helpers.go +0 -28
- package/compiler/expr-call-make.go +93 -343
- package/compiler/expr-call-type-conversion.go +221 -249
- package/compiler/expr-call.go +70 -69
- package/compiler/expr-selector.go +21 -24
- package/compiler/expr.go +3 -60
- package/compiler/protobuf.go +180 -36
- package/compiler/spec-value.go +132 -24
- package/compiler/spec.go +14 -55
- package/compiler/stmt-assign.go +338 -356
- package/compiler/stmt-range.go +4 -24
- package/compiler/stmt.go +92 -203
- package/compiler/type-utils.go +185 -0
- package/compiler/type.go +26 -80
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js +3 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +8 -2
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/fmt/fmt.js +113 -16
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/runtime/runtime.d.ts +1 -1
- package/dist/gs/runtime/runtime.js +1 -1
- package/dist/gs/slices/slices.d.ts +23 -0
- package/dist/gs/slices/slices.js +61 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/go.mod +8 -8
- package/go.sum +14 -14
- package/gs/builtin/slice.ts +5 -2
- package/gs/builtin/type.ts +13 -6
- package/gs/fmt/fmt.test.ts +176 -0
- package/gs/fmt/fmt.ts +109 -18
- package/gs/runtime/runtime.ts +1 -1
- package/gs/slices/slices.ts +68 -0
- package/package.json +3 -3
package/compiler/stmt-assign.go
CHANGED
|
@@ -47,356 +47,6 @@ import (
|
|
|
47
47
|
// It correctly applies `let` for `:=` (define) tokens and handles varRefing and
|
|
48
48
|
// cloning semantics based on type information and analysis.
|
|
49
49
|
func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
50
|
-
// writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
|
|
51
|
-
writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
|
|
52
|
-
// For token.DEFINE (:=), we need to check if any of the variables are already declared
|
|
53
|
-
// In Go, := can be used for redeclaration if at least one variable is new
|
|
54
|
-
if tok == token.DEFINE {
|
|
55
|
-
// For token.DEFINE (:=), we need to handle variable declarations differently
|
|
56
|
-
// In Go, := can redeclare existing variables if at least one variable is new
|
|
57
|
-
|
|
58
|
-
// First, identify which variables are new vs existing
|
|
59
|
-
newVars := make([]bool, len(lhs))
|
|
60
|
-
anyNewVars := false
|
|
61
|
-
allNewVars := true
|
|
62
|
-
|
|
63
|
-
// For multi-variable assignments with :=, we need to determine which variables
|
|
64
|
-
// are already in scope and which are new declarations
|
|
65
|
-
for i, lhsExpr := range lhs {
|
|
66
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
|
|
67
|
-
// In Go, variables declared with := can be redeclared if at least one is new
|
|
68
|
-
// For TypeScript, we need to separately declare new variables
|
|
69
|
-
|
|
70
|
-
// Check if this variable is already in scope
|
|
71
|
-
// - If the variable is used elsewhere before this point, it's existing
|
|
72
|
-
// - Otherwise, it's a new variable being declared
|
|
73
|
-
isNew := true
|
|
74
|
-
|
|
75
|
-
// Check if the variable is used elsewhere in the code
|
|
76
|
-
if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
77
|
-
// If it's in Uses, it's referenced elsewhere, so it exists
|
|
78
|
-
isNew = false
|
|
79
|
-
allNewVars = false
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
newVars[i] = isNew
|
|
83
|
-
if isNew {
|
|
84
|
-
anyNewVars = true
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Get function return types if available
|
|
90
|
-
var resultTypes []*types.Var
|
|
91
|
-
if callExpr.Fun != nil {
|
|
92
|
-
if funcType, ok := c.pkg.TypesInfo.TypeOf(callExpr.Fun).Underlying().(*types.Signature); ok {
|
|
93
|
-
if funcType.Results() != nil && funcType.Results().Len() > 0 {
|
|
94
|
-
for i := 0; i < funcType.Results().Len(); i++ {
|
|
95
|
-
resultTypes = append(resultTypes, funcType.Results().At(i))
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if allNewVars && anyNewVars {
|
|
102
|
-
c.tsw.WriteLiterally("let [")
|
|
103
|
-
|
|
104
|
-
for i, lhsExpr := range lhs {
|
|
105
|
-
if i != 0 {
|
|
106
|
-
c.tsw.WriteLiterally(", ")
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
110
|
-
if ident.Name == "_" {
|
|
111
|
-
// For underscore variables, use empty slots in destructuring pattern
|
|
112
|
-
} else {
|
|
113
|
-
c.WriteIdent(ident, false)
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
c.WriteValueExpr(lhsExpr)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
c.tsw.WriteLiterally("] = ")
|
|
120
|
-
c.WriteValueExpr(callExpr)
|
|
121
|
-
c.tsw.WriteLine("")
|
|
122
|
-
return nil
|
|
123
|
-
} else if anyNewVars {
|
|
124
|
-
// If only some variables are new, declare them separately before the assignment
|
|
125
|
-
// Declare each new variable with appropriate type
|
|
126
|
-
for i, lhsExpr := range lhs {
|
|
127
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && newVars[i] {
|
|
128
|
-
c.tsw.WriteLiterally("let ")
|
|
129
|
-
c.WriteIdent(ident, false)
|
|
130
|
-
|
|
131
|
-
// Add type annotation if we have type information
|
|
132
|
-
if i < len(resultTypes) {
|
|
133
|
-
c.tsw.WriteLiterally(": ")
|
|
134
|
-
c.WriteGoType(resultTypes[i].Type(), GoTypeContextGeneral)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
c.tsw.WriteLine("")
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// First, collect all the selector expressions to identify variables that need to be initialized
|
|
144
|
-
hasSelectors := false
|
|
145
|
-
for _, lhsExpr := range lhs {
|
|
146
|
-
if _, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
147
|
-
hasSelectors = true
|
|
148
|
-
break
|
|
149
|
-
}
|
|
150
|
-
if _, ok := lhsExpr.(*ast.StarExpr); ok {
|
|
151
|
-
hasSelectors = true
|
|
152
|
-
break
|
|
153
|
-
}
|
|
154
|
-
if _, ok := lhsExpr.(*ast.IndexExpr); ok {
|
|
155
|
-
hasSelectors = true
|
|
156
|
-
break
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// If we have selector expressions, we need to ensure variables are initialized
|
|
161
|
-
// before the destructuring assignment
|
|
162
|
-
if hasSelectors {
|
|
163
|
-
c.tsw.WriteLiterally("{")
|
|
164
|
-
c.tsw.WriteLine("")
|
|
165
|
-
|
|
166
|
-
// Write a temporary variable to hold the function call result
|
|
167
|
-
c.tsw.WriteLiterally(" const _tmp = ")
|
|
168
|
-
if err := c.WriteValueExpr(callExpr); err != nil {
|
|
169
|
-
return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
|
|
170
|
-
}
|
|
171
|
-
c.tsw.WriteLine("")
|
|
172
|
-
|
|
173
|
-
for i, lhsExpr := range lhs {
|
|
174
|
-
// Skip underscore variables
|
|
175
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
|
|
176
|
-
continue
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Write the LHS with indentation
|
|
180
|
-
c.tsw.WriteLiterally(" ")
|
|
181
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
182
|
-
c.WriteIdent(ident, false)
|
|
183
|
-
} else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
184
|
-
if err := c.WriteValueExpr(selectorExpr); err != nil {
|
|
185
|
-
return fmt.Errorf("failed to write selector expression in LHS: %w", err)
|
|
186
|
-
}
|
|
187
|
-
} else if starExpr, ok := lhsExpr.(*ast.StarExpr); ok {
|
|
188
|
-
// Handle pointer dereference assignment: *p = value becomes p!.value = value
|
|
189
|
-
// Write the pointer variable directly without using WriteValueExpr
|
|
190
|
-
// because we don't want automatic .value access here
|
|
191
|
-
switch operand := starExpr.X.(type) {
|
|
192
|
-
case *ast.Ident:
|
|
193
|
-
// Write identifier without .value access
|
|
194
|
-
c.WriteIdent(operand, false)
|
|
195
|
-
default:
|
|
196
|
-
// For other expressions, use WriteValueExpr
|
|
197
|
-
if err := c.WriteValueExpr(starExpr.X); err != nil {
|
|
198
|
-
return fmt.Errorf("failed to write star expression X in LHS: %w", err)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
c.tsw.WriteLiterally("!.value")
|
|
202
|
-
} else if indexExpr, ok := lhsExpr.(*ast.IndexExpr); ok {
|
|
203
|
-
// Handle index expressions (e.g., arr[i], slice[j]) by using WriteValueExpr
|
|
204
|
-
if err := c.WriteValueExpr(indexExpr); err != nil {
|
|
205
|
-
return fmt.Errorf("failed to write index expression in LHS: %w", err)
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Write the assignment
|
|
212
|
-
c.tsw.WriteLiterallyf(" = _tmp[%d]", i)
|
|
213
|
-
// Always add a newline after each assignment
|
|
214
|
-
c.tsw.WriteLine("")
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Close the block scope
|
|
218
|
-
c.tsw.WriteLiterally("}")
|
|
219
|
-
c.tsw.WriteLine("")
|
|
220
|
-
|
|
221
|
-
return nil
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// For simple cases without selector expressions, use array destructuring
|
|
225
|
-
// Add semicolon before destructuring assignment to prevent TypeScript
|
|
226
|
-
// from interpreting it as array access on the previous line
|
|
227
|
-
if tok != token.DEFINE {
|
|
228
|
-
c.tsw.WriteLiterally(";")
|
|
229
|
-
}
|
|
230
|
-
c.tsw.WriteLiterally("[")
|
|
231
|
-
|
|
232
|
-
// Find the last non-blank identifier to avoid trailing commas
|
|
233
|
-
lastNonBlankIndex := -1
|
|
234
|
-
for i := len(lhs) - 1; i >= 0; i-- {
|
|
235
|
-
if ident, ok := lhs[i].(*ast.Ident); !ok || ident.Name != "_" {
|
|
236
|
-
lastNonBlankIndex = i
|
|
237
|
-
break
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
for i, lhsExpr := range lhs {
|
|
242
|
-
// Write comma before non-first elements
|
|
243
|
-
if i > 0 {
|
|
244
|
-
c.tsw.WriteLiterally(", ")
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
248
|
-
// For underscore variables, use empty slots in destructuring pattern
|
|
249
|
-
if ident.Name != "_" {
|
|
250
|
-
c.WriteIdent(ident, false)
|
|
251
|
-
}
|
|
252
|
-
// For blank identifiers, we write nothing (empty slot)
|
|
253
|
-
} else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
254
|
-
// Handle selector expressions (e.g., a.b) by using WriteValueExpr
|
|
255
|
-
if err := c.WriteValueExpr(selectorExpr); err != nil {
|
|
256
|
-
return fmt.Errorf("failed to write selector expression in LHS: %w", err)
|
|
257
|
-
}
|
|
258
|
-
} else if starExpr, ok := lhsExpr.(*ast.StarExpr); ok {
|
|
259
|
-
// Handle pointer dereference in destructuring: *p becomes p!.value
|
|
260
|
-
// Write the pointer variable directly without using WriteValueExpr
|
|
261
|
-
// because we don't want automatic .value access here
|
|
262
|
-
switch operand := starExpr.X.(type) {
|
|
263
|
-
case *ast.Ident:
|
|
264
|
-
// Write identifier without .value access
|
|
265
|
-
c.WriteIdent(operand, false)
|
|
266
|
-
default:
|
|
267
|
-
// For other expressions, use WriteValueExpr
|
|
268
|
-
if err := c.WriteValueExpr(starExpr.X); err != nil {
|
|
269
|
-
return fmt.Errorf("failed to write star expression X in destructuring: %w", err)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
c.tsw.WriteLiterally("!.value")
|
|
273
|
-
} else if indexExpr, ok := lhsExpr.(*ast.IndexExpr); ok {
|
|
274
|
-
// Handle index expressions (e.g., arr[i], slice[j]) by using WriteValueExpr
|
|
275
|
-
if err := c.WriteValueExpr(indexExpr); err != nil {
|
|
276
|
-
return fmt.Errorf("failed to write index expression in destructuring: %w", err)
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
// Should not happen for valid Go code in this context, but handle defensively
|
|
280
|
-
return errors.Errorf("unhandled LHS expression in destructuring: %T", lhsExpr)
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Stop writing if we've reached the last non-blank element
|
|
284
|
-
if i == lastNonBlankIndex {
|
|
285
|
-
break
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
c.tsw.WriteLiterally("] = ")
|
|
289
|
-
|
|
290
|
-
c.WriteValueExpr(callExpr)
|
|
291
|
-
|
|
292
|
-
c.tsw.WriteLine("")
|
|
293
|
-
return nil
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
|
|
297
|
-
// Uses array destructuring with the tuple-returning $.mapGet function
|
|
298
|
-
writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
|
|
299
|
-
// First check that we have exactly two LHS expressions (value and exists)
|
|
300
|
-
if len(lhs) != 2 {
|
|
301
|
-
return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Check for blank identifiers
|
|
305
|
-
valueIsBlank := false
|
|
306
|
-
existsIsBlank := false
|
|
307
|
-
|
|
308
|
-
if valIdent, ok := lhs[0].(*ast.Ident); ok && valIdent.Name == "_" {
|
|
309
|
-
valueIsBlank = true
|
|
310
|
-
}
|
|
311
|
-
if existsIdent, ok := lhs[1].(*ast.Ident); ok && existsIdent.Name == "_" {
|
|
312
|
-
existsIsBlank = true
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Use array destructuring with mapGet tuple return
|
|
316
|
-
if tok == token.DEFINE {
|
|
317
|
-
c.tsw.WriteLiterally("let ")
|
|
318
|
-
} else {
|
|
319
|
-
// Add semicolon before destructuring assignment to prevent TypeScript
|
|
320
|
-
// from interpreting it as array access on the previous line
|
|
321
|
-
c.tsw.WriteLiterally(";")
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
c.tsw.WriteLiterally("[")
|
|
325
|
-
|
|
326
|
-
// Write LHS variables, handling blanks
|
|
327
|
-
if !valueIsBlank {
|
|
328
|
-
if err := c.WriteValueExpr(lhs[0]); err != nil {
|
|
329
|
-
return err
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
// Note: for blank identifiers, we just omit the variable name entirely
|
|
333
|
-
|
|
334
|
-
c.tsw.WriteLiterally(", ")
|
|
335
|
-
|
|
336
|
-
if !existsIsBlank {
|
|
337
|
-
if err := c.WriteValueExpr(lhs[1]); err != nil {
|
|
338
|
-
return err
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// Note: for blank identifiers, we just omit the variable name entirely
|
|
342
|
-
|
|
343
|
-
c.tsw.WriteLiterally("] = $.mapGet(")
|
|
344
|
-
|
|
345
|
-
// Write map expression
|
|
346
|
-
if err := c.WriteValueExpr(indexExpr.X); err != nil {
|
|
347
|
-
return err
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
c.tsw.WriteLiterally(", ")
|
|
351
|
-
|
|
352
|
-
// Write key expression
|
|
353
|
-
if err := c.WriteValueExpr(indexExpr.Index); err != nil {
|
|
354
|
-
return err
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
c.tsw.WriteLiterally(", ")
|
|
358
|
-
|
|
359
|
-
// Write the zero value for the map's value type
|
|
360
|
-
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
361
|
-
if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
362
|
-
c.WriteZeroValueForType(mapType.Elem())
|
|
363
|
-
} else if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
|
|
364
|
-
// Handle type parameter constrained to be a map type
|
|
365
|
-
constraint := typeParam.Constraint()
|
|
366
|
-
if constraint != nil {
|
|
367
|
-
underlying := constraint.Underlying()
|
|
368
|
-
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
369
|
-
if hasMapConstraint(iface) {
|
|
370
|
-
// Get the value type from the constraint
|
|
371
|
-
mapValueType := getMapValueTypeFromConstraint(iface)
|
|
372
|
-
if mapValueType != nil {
|
|
373
|
-
c.WriteZeroValueForType(mapValueType)
|
|
374
|
-
} else {
|
|
375
|
-
c.tsw.WriteLiterally("null")
|
|
376
|
-
}
|
|
377
|
-
} else {
|
|
378
|
-
c.tsw.WriteLiterally("null")
|
|
379
|
-
}
|
|
380
|
-
} else {
|
|
381
|
-
c.tsw.WriteLiterally("null")
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
c.tsw.WriteLiterally("null")
|
|
385
|
-
}
|
|
386
|
-
} else {
|
|
387
|
-
// Fallback zero value if type info is missing or not a map
|
|
388
|
-
c.tsw.WriteLiterally("null")
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
c.tsw.WriteLiterally("null")
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
c.tsw.WriteLiterally(")")
|
|
395
|
-
c.tsw.WriteLine("")
|
|
396
|
-
|
|
397
|
-
return nil
|
|
398
|
-
}
|
|
399
|
-
|
|
400
50
|
// Handle multi-variable assignment from a single expression.
|
|
401
51
|
if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
|
|
402
52
|
rhsExpr := exp.Rhs[0]
|
|
@@ -420,7 +70,7 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
420
70
|
return nil
|
|
421
71
|
}
|
|
422
72
|
// Handle general function calls that return multiple values
|
|
423
|
-
return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
|
|
73
|
+
return c.writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
|
|
424
74
|
}
|
|
425
75
|
|
|
426
76
|
if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
|
|
@@ -430,20 +80,20 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
430
80
|
if len(exp.Lhs) == 2 {
|
|
431
81
|
// Get the type of the indexed expression
|
|
432
82
|
if c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
433
|
-
|
|
83
|
+
v, ok := c.pkg.TypesInfo.Types[indexExpr.X]
|
|
434
84
|
if ok {
|
|
435
85
|
// Check if it's a concrete map type
|
|
436
|
-
if _, isMap :=
|
|
437
|
-
return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
86
|
+
if _, isMap := v.Type.Underlying().(*types.Map); isMap {
|
|
87
|
+
return c.writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
438
88
|
}
|
|
439
89
|
// Check if it's a type parameter constrained to be a map type
|
|
440
|
-
if typeParam, isTypeParam :=
|
|
90
|
+
if typeParam, isTypeParam := v.Type.(*types.TypeParam); isTypeParam {
|
|
441
91
|
constraint := typeParam.Constraint()
|
|
442
92
|
if constraint != nil {
|
|
443
93
|
underlying := constraint.Underlying()
|
|
444
94
|
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
445
95
|
if hasMapConstraint(iface) {
|
|
446
|
-
return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
96
|
+
return c.writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
447
97
|
}
|
|
448
98
|
}
|
|
449
99
|
}
|
|
@@ -554,3 +204,335 @@ func (c *GoToTSCompiler) writeInlineComment(node ast.Node) {
|
|
|
554
204
|
}
|
|
555
205
|
}
|
|
556
206
|
}
|
|
207
|
+
|
|
208
|
+
// writeLHSTarget writes an LHS target expression for assignment contexts.
|
|
209
|
+
// It preserves the exact behavior used in WriteStmtAssign for selector, star, and index expressions,
|
|
210
|
+
// and avoids adding .value on identifiers.
|
|
211
|
+
func (c *GoToTSCompiler) writeLHSTarget(lhsExpr ast.Expr) error {
|
|
212
|
+
switch t := lhsExpr.(type) {
|
|
213
|
+
case *ast.Ident:
|
|
214
|
+
// Caller should have handled blank identifiers; write name without .value
|
|
215
|
+
c.WriteIdent(t, false)
|
|
216
|
+
return nil
|
|
217
|
+
case *ast.SelectorExpr:
|
|
218
|
+
if err := c.WriteValueExpr(t); err != nil {
|
|
219
|
+
return fmt.Errorf("failed to write selector expression in LHS: %w", err)
|
|
220
|
+
}
|
|
221
|
+
return nil
|
|
222
|
+
case *ast.StarExpr:
|
|
223
|
+
// Handle pointer dereference assignment: *p = value becomes p!.value = value
|
|
224
|
+
// Write the pointer variable directly without using WriteValueExpr when it's an identifier
|
|
225
|
+
switch operand := t.X.(type) {
|
|
226
|
+
case *ast.Ident:
|
|
227
|
+
c.WriteIdent(operand, false)
|
|
228
|
+
default:
|
|
229
|
+
if err := c.WriteValueExpr(t.X); err != nil {
|
|
230
|
+
return fmt.Errorf("failed to write star expression X in LHS: %w", err)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
c.tsw.WriteLiterally("!.value")
|
|
234
|
+
return nil
|
|
235
|
+
case *ast.IndexExpr:
|
|
236
|
+
if err := c.WriteValueExpr(t); err != nil {
|
|
237
|
+
return fmt.Errorf("failed to write index expression in LHS: %w", err)
|
|
238
|
+
}
|
|
239
|
+
return nil
|
|
240
|
+
default:
|
|
241
|
+
return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// lhsHasComplexTargets returns true if any LHS expression is a selector, star (dereference), or index expression.
|
|
246
|
+
func (c *GoToTSCompiler) lhsHasComplexTargets(lhs []ast.Expr) bool {
|
|
247
|
+
for _, e := range lhs {
|
|
248
|
+
switch e.(type) {
|
|
249
|
+
case *ast.SelectorExpr, *ast.StarExpr, *ast.IndexExpr:
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
|
|
257
|
+
func (c *GoToTSCompiler) writeMultiVarAssignFromCall(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
|
|
258
|
+
// For token.DEFINE (:=), we need to check if any of the variables are already declared
|
|
259
|
+
// In Go, := can be used for redeclaration if at least one variable is new
|
|
260
|
+
if tok == token.DEFINE {
|
|
261
|
+
// For token.DEFINE (:=), we need to handle variable declarations differently
|
|
262
|
+
// In Go, := can redeclare existing variables if at least one is new
|
|
263
|
+
|
|
264
|
+
// First, identify which variables are new vs existing
|
|
265
|
+
newVars := make([]bool, len(lhs))
|
|
266
|
+
anyNewVars := false
|
|
267
|
+
allNewVars := true
|
|
268
|
+
|
|
269
|
+
// For multi-variable assignments with :=, we need to determine which variables
|
|
270
|
+
// are already in scope and which are new declarations
|
|
271
|
+
for i, lhsExpr := range lhs {
|
|
272
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
|
|
273
|
+
// In Go, variables declared with := can be redeclared if at least one is new
|
|
274
|
+
// For TypeScript, we need to separately declare new variables
|
|
275
|
+
|
|
276
|
+
// Check if this variable is already in scope
|
|
277
|
+
// - If the variable is used elsewhere before this point, it's existing
|
|
278
|
+
// - Otherwise, it's a new variable being declared
|
|
279
|
+
isNew := true
|
|
280
|
+
|
|
281
|
+
// Check if the variable is used elsewhere in the code
|
|
282
|
+
if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
283
|
+
// If it's in Uses, it's referenced elsewhere, so it exists
|
|
284
|
+
isNew = false
|
|
285
|
+
allNewVars = false
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
newVars[i] = isNew
|
|
289
|
+
if isNew {
|
|
290
|
+
anyNewVars = true
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get function return types if available
|
|
296
|
+
var resultTypes []*types.Var
|
|
297
|
+
if callExpr.Fun != nil {
|
|
298
|
+
if funcType, ok := c.pkg.TypesInfo.TypeOf(callExpr.Fun).Underlying().(*types.Signature); ok {
|
|
299
|
+
if funcType.Results() != nil && funcType.Results().Len() > 0 {
|
|
300
|
+
for i := 0; i < funcType.Results().Len(); i++ {
|
|
301
|
+
resultTypes = append(resultTypes, funcType.Results().At(i))
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if allNewVars && anyNewVars {
|
|
308
|
+
c.tsw.WriteLiterally("let [")
|
|
309
|
+
|
|
310
|
+
for i, lhsExpr := range lhs {
|
|
311
|
+
if i != 0 {
|
|
312
|
+
c.tsw.WriteLiterally(", ")
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
316
|
+
if ident.Name == "_" {
|
|
317
|
+
// For underscore variables, use empty slots in destructuring pattern
|
|
318
|
+
} else {
|
|
319
|
+
c.WriteIdent(ident, false)
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
c.WriteValueExpr(lhsExpr)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
c.tsw.WriteLiterally("] = ")
|
|
326
|
+
c.WriteValueExpr(callExpr)
|
|
327
|
+
c.tsw.WriteLine("")
|
|
328
|
+
return nil
|
|
329
|
+
} else if anyNewVars {
|
|
330
|
+
// If only some variables are new, declare them separately before the assignment
|
|
331
|
+
// Declare each new variable with appropriate type
|
|
332
|
+
for i, lhsExpr := range lhs {
|
|
333
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && newVars[i] {
|
|
334
|
+
c.tsw.WriteLiterally("let ")
|
|
335
|
+
c.WriteIdent(ident, false)
|
|
336
|
+
// Add type annotation if we have type information
|
|
337
|
+
if i < len(resultTypes) {
|
|
338
|
+
c.tsw.WriteLiterally(": ")
|
|
339
|
+
c.WriteGoType(resultTypes[i].Type(), GoTypeContextGeneral)
|
|
340
|
+
}
|
|
341
|
+
c.tsw.WriteLine("")
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// First, collect all the selector expressions to identify variables that need to be initialized
|
|
348
|
+
hasSelectors := c.lhsHasComplexTargets(lhs)
|
|
349
|
+
|
|
350
|
+
// If we have selector expressions, we need to ensure variables are initialized
|
|
351
|
+
// before the destructuring assignment
|
|
352
|
+
if hasSelectors {
|
|
353
|
+
c.tsw.WriteLiterally("{")
|
|
354
|
+
c.tsw.WriteLine("")
|
|
355
|
+
|
|
356
|
+
// Write a temporary variable to hold the function call result
|
|
357
|
+
c.tsw.WriteLiterally(" const _tmp = ")
|
|
358
|
+
if err := c.WriteValueExpr(callExpr); err != nil {
|
|
359
|
+
return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
|
|
360
|
+
}
|
|
361
|
+
c.tsw.WriteLine("")
|
|
362
|
+
|
|
363
|
+
for i, lhsExpr := range lhs {
|
|
364
|
+
// Skip underscore variables
|
|
365
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Write the LHS with indentation
|
|
370
|
+
c.tsw.WriteLiterally(" ")
|
|
371
|
+
if err := c.writeLHSTarget(lhsExpr); err != nil {
|
|
372
|
+
return err
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Write the assignment
|
|
376
|
+
c.tsw.WriteLiterallyf(" = _tmp[%d]", i)
|
|
377
|
+
// Always add a newline after each assignment
|
|
378
|
+
c.tsw.WriteLine("")
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Close the block scope
|
|
382
|
+
c.tsw.WriteLiterally("}")
|
|
383
|
+
c.tsw.WriteLine("")
|
|
384
|
+
|
|
385
|
+
return nil
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// For simple cases without selector expressions, use array destructuring
|
|
389
|
+
// Add semicolon before destructuring assignment to prevent TypeScript
|
|
390
|
+
// from interpreting it as array access on the previous line
|
|
391
|
+
if tok != token.DEFINE {
|
|
392
|
+
c.tsw.WriteLiterally(";")
|
|
393
|
+
}
|
|
394
|
+
c.tsw.WriteLiterally("[")
|
|
395
|
+
|
|
396
|
+
// Find the last non-blank identifier to avoid trailing commas
|
|
397
|
+
lastNonBlankIndex := -1
|
|
398
|
+
for i := len(lhs) - 1; i >= 0; i-- {
|
|
399
|
+
if ident, ok := lhs[i].(*ast.Ident); !ok || ident.Name != "_" {
|
|
400
|
+
lastNonBlankIndex = i
|
|
401
|
+
break
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for i, lhsExpr := range lhs {
|
|
406
|
+
// Write comma before non-first elements
|
|
407
|
+
if i > 0 {
|
|
408
|
+
c.tsw.WriteLiterally(", ")
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
412
|
+
// For underscore variables, use empty slots in destructuring pattern
|
|
413
|
+
if ident.Name != "_" {
|
|
414
|
+
c.WriteIdent(ident, false)
|
|
415
|
+
}
|
|
416
|
+
// For blank identifiers, we write nothing (empty slot)
|
|
417
|
+
} else {
|
|
418
|
+
if err := c.writeLHSTarget(lhsExpr); err != nil {
|
|
419
|
+
return err
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Stop writing if we've reached the last non-blank element
|
|
424
|
+
if i == lastNonBlankIndex {
|
|
425
|
+
break
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
c.tsw.WriteLiterally("] = ")
|
|
429
|
+
|
|
430
|
+
c.WriteValueExpr(callExpr)
|
|
431
|
+
|
|
432
|
+
c.tsw.WriteLine("")
|
|
433
|
+
return nil
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
|
|
437
|
+
// Uses array destructuring with the tuple-returning $.mapGet function
|
|
438
|
+
func (c *GoToTSCompiler) writeMapLookupWithExists(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
|
|
439
|
+
// First check that we have exactly two LHS expressions (value and exists)
|
|
440
|
+
if len(lhs) != 2 {
|
|
441
|
+
return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Check for blank identifiers
|
|
445
|
+
valueIsBlank := false
|
|
446
|
+
existsIsBlank := false
|
|
447
|
+
|
|
448
|
+
if valIdent, ok := lhs[0].(*ast.Ident); ok && valIdent.Name == "_" {
|
|
449
|
+
valueIsBlank = true
|
|
450
|
+
}
|
|
451
|
+
if existsIdent, ok := lhs[1].(*ast.Ident); ok && existsIdent.Name == "_" {
|
|
452
|
+
existsIsBlank = true
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Use array destructuring with mapGet tuple return
|
|
456
|
+
if tok == token.DEFINE {
|
|
457
|
+
c.tsw.WriteLiterally("let ")
|
|
458
|
+
} else {
|
|
459
|
+
// Add semicolon before destructuring assignment to prevent TypeScript
|
|
460
|
+
// from interpreting it as array access on the previous line
|
|
461
|
+
c.tsw.WriteLiterally(";")
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
c.tsw.WriteLiterally("[")
|
|
465
|
+
|
|
466
|
+
// Write LHS variables, handling blanks
|
|
467
|
+
if !valueIsBlank {
|
|
468
|
+
if err := c.WriteValueExpr(lhs[0]); err != nil {
|
|
469
|
+
return err
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Note: for blank identifiers, we just omit the variable name entirely
|
|
473
|
+
|
|
474
|
+
c.tsw.WriteLiterally(", ")
|
|
475
|
+
|
|
476
|
+
if !existsIsBlank {
|
|
477
|
+
if err := c.WriteValueExpr(lhs[1]); err != nil {
|
|
478
|
+
return err
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Note: for blank identifiers, we just omit the variable name entirely
|
|
482
|
+
|
|
483
|
+
c.tsw.WriteLiterally("] = $.mapGet(")
|
|
484
|
+
|
|
485
|
+
// Write map expression
|
|
486
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil {
|
|
487
|
+
return err
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
c.tsw.WriteLiterally(", ")
|
|
491
|
+
|
|
492
|
+
// Write key expression
|
|
493
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil {
|
|
494
|
+
return err
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
c.tsw.WriteLiterally(", ")
|
|
498
|
+
|
|
499
|
+
// Write the zero value for the map's value type
|
|
500
|
+
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
501
|
+
if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
502
|
+
c.WriteZeroValueForType(mapType.Elem())
|
|
503
|
+
} else if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
|
|
504
|
+
// Handle type parameter constrained to be a map type
|
|
505
|
+
constraint := typeParam.Constraint()
|
|
506
|
+
if constraint != nil {
|
|
507
|
+
underlying := constraint.Underlying()
|
|
508
|
+
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
509
|
+
if hasMapConstraint(iface) {
|
|
510
|
+
// Get the value type from the constraint
|
|
511
|
+
mapValueType := getMapValueTypeFromConstraint(iface)
|
|
512
|
+
if mapValueType != nil {
|
|
513
|
+
c.WriteZeroValueForType(mapValueType)
|
|
514
|
+
} else {
|
|
515
|
+
c.tsw.WriteLiterally("null")
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
c.tsw.WriteLiterally("null")
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
c.tsw.WriteLiterally("null")
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
c.tsw.WriteLiterally("null")
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
// Fallback zero value if type info is missing or not a map
|
|
528
|
+
c.tsw.WriteLiterally("null")
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
c.tsw.WriteLiterally("null")
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
c.tsw.WriteLiterally(")")
|
|
535
|
+
c.tsw.WriteLine("")
|
|
536
|
+
|
|
537
|
+
return nil
|
|
538
|
+
}
|