goscript 0.0.57 → 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.
Files changed (44) hide show
  1. package/README.md +40 -33
  2. package/compiler/analysis.go +115 -19
  3. package/compiler/assignment.go +163 -217
  4. package/compiler/compiler.go +35 -31
  5. package/compiler/composite-lit.go +233 -196
  6. package/compiler/constraint.go +88 -0
  7. package/compiler/decl.go +418 -7
  8. package/compiler/expr-call-async.go +20 -34
  9. package/compiler/expr-call-builtins.go +19 -0
  10. package/compiler/expr-call-helpers.go +0 -28
  11. package/compiler/expr-call-make.go +93 -343
  12. package/compiler/expr-call-type-conversion.go +221 -249
  13. package/compiler/expr-call.go +70 -69
  14. package/compiler/expr-selector.go +21 -24
  15. package/compiler/expr.go +3 -60
  16. package/compiler/protobuf.go +180 -36
  17. package/compiler/spec-value.go +132 -24
  18. package/compiler/spec.go +14 -55
  19. package/compiler/stmt-assign.go +338 -356
  20. package/compiler/stmt-range.go +4 -24
  21. package/compiler/stmt.go +99 -172
  22. package/compiler/type-utils.go +185 -0
  23. package/compiler/type.go +26 -80
  24. package/dist/gs/builtin/slice.d.ts +1 -1
  25. package/dist/gs/builtin/slice.js +3 -0
  26. package/dist/gs/builtin/slice.js.map +1 -1
  27. package/dist/gs/builtin/type.js +8 -2
  28. package/dist/gs/builtin/type.js.map +1 -1
  29. package/dist/gs/fmt/fmt.js +113 -16
  30. package/dist/gs/fmt/fmt.js.map +1 -1
  31. package/dist/gs/runtime/runtime.d.ts +1 -1
  32. package/dist/gs/runtime/runtime.js +1 -1
  33. package/dist/gs/slices/slices.d.ts +23 -0
  34. package/dist/gs/slices/slices.js +61 -0
  35. package/dist/gs/slices/slices.js.map +1 -1
  36. package/go.mod +8 -8
  37. package/go.sum +14 -14
  38. package/gs/builtin/slice.ts +5 -2
  39. package/gs/builtin/type.ts +13 -6
  40. package/gs/fmt/fmt.test.ts +176 -0
  41. package/gs/fmt/fmt.ts +109 -18
  42. package/gs/runtime/runtime.ts +1 -1
  43. package/gs/slices/slices.ts +68 -0
  44. package/package.json +3 -3
@@ -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
- tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
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 := tv.Type.Underlying().(*types.Map); 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 := tv.Type.(*types.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
+ }