goscript 0.0.32 → 0.0.34

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 (43) hide show
  1. package/compiler/analysis.go +2 -2
  2. package/compiler/assignment.go +26 -0
  3. package/compiler/builtin_test.go +2 -0
  4. package/compiler/compiler.go +12 -2
  5. package/compiler/compiler_test.go +0 -53
  6. package/compiler/expr-call.go +229 -2
  7. package/compiler/expr.go +66 -1
  8. package/compiler/lit.go +1 -1
  9. package/compiler/spec.go +6 -0
  10. package/compiler/stmt-assign.go +106 -90
  11. package/compiler/stmt-for.go +78 -1
  12. package/compiler/stmt-range.go +333 -461
  13. package/compiler/stmt.go +20 -0
  14. package/compiler/type.go +11 -8
  15. package/dist/gs/builtin/builtin.d.ts +7 -0
  16. package/dist/gs/builtin/builtin.js +30 -0
  17. package/dist/gs/builtin/builtin.js.map +1 -1
  18. package/dist/gs/builtin/map.d.ts +4 -4
  19. package/dist/gs/builtin/map.js +12 -6
  20. package/dist/gs/builtin/map.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +7 -7
  22. package/dist/gs/builtin/slice.js +19 -9
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/maps/index.d.ts +2 -0
  25. package/dist/gs/maps/index.js +3 -0
  26. package/dist/gs/maps/index.js.map +1 -0
  27. package/dist/gs/maps/iter.gs.d.ts +7 -0
  28. package/dist/gs/maps/iter.gs.js +65 -0
  29. package/dist/gs/maps/iter.gs.js.map +1 -0
  30. package/dist/gs/maps/maps.gs.d.ts +7 -0
  31. package/dist/gs/maps/maps.gs.js +79 -0
  32. package/dist/gs/maps/maps.gs.js.map +1 -0
  33. package/dist/gs/slices/slices.d.ts +6 -0
  34. package/dist/gs/slices/slices.js +8 -0
  35. package/dist/gs/slices/slices.js.map +1 -1
  36. package/gs/builtin/builtin.ts +38 -0
  37. package/gs/builtin/map.ts +10 -9
  38. package/gs/builtin/slice.ts +23 -11
  39. package/gs/maps/index.ts +2 -0
  40. package/gs/maps/iter.gs.ts +71 -0
  41. package/gs/maps/maps.gs.ts +87 -0
  42. package/gs/slices/slices.ts +9 -0
  43. package/package.json +1 -1
@@ -276,122 +276,106 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
276
276
  }
277
277
 
278
278
  // writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
279
- // Note: We don't use WriteIndexExpr here because we need to handle .has() and .get() separately
279
+ // Uses array destructuring with the tuple-returning $.mapGet function
280
280
  writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
281
281
  // First check that we have exactly two LHS expressions (value and exists)
282
282
  if len(lhs) != 2 {
283
283
  return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
284
284
  }
285
285
 
286
- // Check for blank identifiers and get variable names
286
+ // Check for blank identifiers
287
287
  valueIsBlank := false
288
288
  existsIsBlank := false
289
- var valueName string
290
- var existsName string
291
289
 
292
- if valIdent, ok := lhs[0].(*ast.Ident); ok {
293
- if valIdent.Name == "_" {
294
- valueIsBlank = true
295
- } else {
296
- valueName = valIdent.Name
297
- }
298
- } else {
299
- return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
290
+ if valIdent, ok := lhs[0].(*ast.Ident); ok && valIdent.Name == "_" {
291
+ valueIsBlank = true
292
+ }
293
+ if existsIdent, ok := lhs[1].(*ast.Ident); ok && existsIdent.Name == "_" {
294
+ existsIsBlank = true
300
295
  }
301
296
 
302
- if existsIdent, ok := lhs[1].(*ast.Ident); ok {
303
- if existsIdent.Name == "_" {
304
- existsIsBlank = true
305
- } else {
306
- existsName = existsIdent.Name
307
- }
297
+ // Use array destructuring with mapGet tuple return
298
+ if tok == token.DEFINE {
299
+ c.tsw.WriteLiterally("let ")
308
300
  } else {
309
- return fmt.Errorf("unhandled LHS expression type for exists in map comma-ok: %T", lhs[1])
301
+ // Add semicolon before destructuring assignment to prevent TypeScript
302
+ // from interpreting it as array access on the previous line
303
+ c.tsw.WriteLiterally(";")
310
304
  }
311
305
 
312
- // Declare variables if using := and not blank
313
- if tok == token.DEFINE {
314
- if !valueIsBlank {
315
- c.tsw.WriteLiterally("let ")
316
- c.tsw.WriteLiterally(valueName)
317
- // TODO: Add type annotation based on map value type
318
- c.tsw.WriteLine("")
319
- }
320
- if !existsIsBlank {
321
- c.tsw.WriteLiterally("let ")
322
- c.tsw.WriteLiterally(existsName)
323
- c.tsw.WriteLiterally(": boolean") // exists is always boolean
324
- c.tsw.WriteLine("")
306
+ c.tsw.WriteLiterally("[")
307
+
308
+ // Write LHS variables, handling blanks
309
+ if !valueIsBlank {
310
+ if err := c.WriteValueExpr(lhs[0]); err != nil {
311
+ return err
325
312
  }
326
313
  }
314
+ // Note: for blank identifiers, we just omit the variable name entirely
315
+
316
+ c.tsw.WriteLiterally(", ")
327
317
 
328
- // Assign 'exists'
329
318
  if !existsIsBlank {
330
- c.tsw.WriteLiterally(existsName)
331
- c.tsw.WriteLiterally(" = ")
332
- c.tsw.WriteLiterally("$.mapHas(")
333
- if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
334
- return err
335
- }
336
- c.tsw.WriteLiterally(", ")
337
- if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
319
+ if err := c.WriteValueExpr(lhs[1]); err != nil {
338
320
  return err
339
321
  }
340
- c.tsw.WriteLiterally(")")
341
- c.tsw.WriteLine("")
342
322
  }
323
+ // Note: for blank identifiers, we just omit the variable name entirely
343
324
 
344
- // Assign 'value'
345
- if !valueIsBlank {
346
- c.tsw.WriteLiterally(valueName)
347
- c.tsw.WriteLiterally(" = ")
348
- c.tsw.WriteLiterally("$.mapGet(")
349
- if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
350
- return err
351
- }
352
- c.tsw.WriteLiterally(", ")
353
- if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
354
- return err
355
- }
356
- c.tsw.WriteLiterally(", ")
357
- // Write the zero value for the map's value type
358
- if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
359
- if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
360
- c.WriteZeroValueForType(mapType.Elem())
325
+ c.tsw.WriteLiterally("] = $.mapGet(")
326
+
327
+ // Write map expression
328
+ if err := c.WriteValueExpr(indexExpr.X); err != nil {
329
+ return err
330
+ }
331
+
332
+ c.tsw.WriteLiterally(", ")
333
+
334
+ // Write key expression
335
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil {
336
+ return err
337
+ }
338
+
339
+ c.tsw.WriteLiterally(", ")
340
+
341
+ // Write the zero value for the map's value type
342
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
343
+ if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
344
+ c.WriteZeroValueForType(mapType.Elem())
345
+ } else if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
346
+ // Handle type parameter constrained to be a map type
347
+ constraint := typeParam.Constraint()
348
+ if constraint != nil {
349
+ underlying := constraint.Underlying()
350
+ if iface, isInterface := underlying.(*types.Interface); isInterface {
351
+ if hasMapConstraint(iface) {
352
+ // Get the value type from the constraint
353
+ mapValueType := getMapValueTypeFromConstraint(iface)
354
+ if mapValueType != nil {
355
+ c.WriteZeroValueForType(mapValueType)
356
+ } else {
357
+ c.tsw.WriteLiterally("null")
358
+ }
359
+ } else {
360
+ c.tsw.WriteLiterally("null")
361
+ }
362
+ } else {
363
+ c.tsw.WriteLiterally("null")
364
+ }
361
365
  } else {
362
- // Fallback zero value if type info is missing or not a map
363
366
  c.tsw.WriteLiterally("null")
364
367
  }
365
368
  } else {
369
+ // Fallback zero value if type info is missing or not a map
366
370
  c.tsw.WriteLiterally("null")
367
371
  }
368
- c.tsw.WriteLiterally(")")
369
- c.tsw.WriteLine("")
370
- } else if existsIsBlank {
371
- // If both are blank, still evaluate for side effects (though .has/.get are usually pure)
372
- // We add a ; otherwise TypeScript thinks we are invoking a function.
373
- c.tsw.WriteLiterally(";(") // Wrap in parens to make it an expression statement
374
- c.tsw.WriteLiterally("$.mapHas(")
375
- if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
376
- return err
377
- }
378
- c.tsw.WriteLiterally(", ")
379
- if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
380
- return err
381
- }
382
- c.tsw.WriteLiterally("), ") // Evaluate .has
383
- c.tsw.WriteLiterally("$.mapGet(")
384
- if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
385
- return err
386
- }
387
- c.tsw.WriteLiterally(", ")
388
- if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
389
- return err
390
- }
391
- c.tsw.WriteLiterally(", null))") // Evaluate .get with null as default
392
- c.tsw.WriteLine("")
372
+ } else {
373
+ c.tsw.WriteLiterally("null")
393
374
  }
394
375
 
376
+ c.tsw.WriteLiterally(")")
377
+ c.tsw.WriteLine("")
378
+
395
379
  return nil
396
380
  }
397
381
 
@@ -417,6 +401,8 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
417
401
  }
418
402
  return nil
419
403
  }
404
+ // Handle general function calls that return multiple values
405
+ return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
420
406
  }
421
407
 
422
408
  if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
@@ -428,10 +414,22 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
428
414
  if c.pkg != nil && c.pkg.TypesInfo != nil {
429
415
  tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
430
416
  if ok {
431
- // Check if it's a map type
417
+ // Check if it's a concrete map type
432
418
  if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
433
419
  return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
434
420
  }
421
+ // Check if it's a type parameter constrained to be a map type
422
+ if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
423
+ constraint := typeParam.Constraint()
424
+ if constraint != nil {
425
+ underlying := constraint.Underlying()
426
+ if iface, isInterface := underlying.(*types.Interface); isInterface {
427
+ if hasMapConstraint(iface) {
428
+ return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
429
+ }
430
+ }
431
+ }
432
+ }
435
433
  }
436
434
  }
437
435
  }
@@ -441,8 +439,6 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
441
439
  return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
442
440
  }
443
441
  // If LHS count is not 2, fall through to error or other handling
444
- } else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
445
- return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
446
442
  }
447
443
  // If none of the specific multi-assign patterns match, fall through to the error check below
448
444
  }
@@ -463,7 +459,27 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
463
459
 
464
460
  // Ensure LHS and RHS have the same length for valid Go code in these cases
465
461
  if len(exp.Lhs) != len(exp.Rhs) {
466
- return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
462
+ // Special case: allow multiple LHS with single RHS if RHS can produce multiple values
463
+ // This handles cases like: x, y := getValue() where getValue() returns multiple values
464
+ // or other expressions that can produce multiple values
465
+ if len(exp.Rhs) == 1 {
466
+ // Allow single RHS expressions that can produce multiple values:
467
+ // - Function calls that return multiple values
468
+ // - Type assertions with comma-ok
469
+ // - Map lookups with comma-ok
470
+ // - Channel receives with comma-ok
471
+ // The Go type checker should have already verified this is valid
472
+ rhsExpr := exp.Rhs[0]
473
+ switch rhsExpr.(type) {
474
+ case *ast.CallExpr, *ast.TypeAssertExpr, *ast.IndexExpr, *ast.UnaryExpr:
475
+ // These expression types can potentially produce multiple values
476
+ // Let the general assignment logic handle them
477
+ default:
478
+ return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
479
+ }
480
+ } else {
481
+ return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
482
+ }
467
483
  }
468
484
 
469
485
  // Handle multi-variable assignment (e.g., swaps) using writeAssignmentCore
@@ -4,6 +4,7 @@ import (
4
4
  "fmt"
5
5
  "go/ast"
6
6
  "go/token"
7
+ "go/types"
7
8
 
8
9
  "github.com/pkg/errors"
9
10
  )
@@ -64,7 +65,25 @@ func (c *GoToTSCompiler) WriteStmtForInit(stmt ast.Stmt) error {
64
65
  case *ast.AssignStmt:
65
66
  // Handle assignment in init (e.g., i := 0 or i = 0)
66
67
  // For TypeScript for-loop init, we need to handle multi-variable declarations differently
67
- if s.Tok == token.DEFINE && len(s.Lhs) > 1 && len(s.Rhs) > 0 {
68
+ if s.Tok == token.DEFINE && len(s.Lhs) > 1 && len(s.Rhs) == 1 {
69
+ // Handle the case where we have multiple LHS variables but only one RHS expression
70
+ // Use array destructuring for all cases including map comma-ok idiom
71
+ rhsExpr := s.Rhs[0]
72
+ c.tsw.WriteLiterally("let [")
73
+ for i, lhs := range s.Lhs {
74
+ if i > 0 {
75
+ c.tsw.WriteLiterally(", ")
76
+ }
77
+ if err := c.WriteValueExpr(lhs); err != nil {
78
+ return err
79
+ }
80
+ }
81
+ c.tsw.WriteLiterally("] = ")
82
+ if err := c.writeDestructuringValue(rhsExpr); err != nil {
83
+ return err
84
+ }
85
+ return nil
86
+ } else if s.Tok == token.DEFINE && len(s.Lhs) > 1 && len(s.Rhs) > 0 {
68
87
  // For loop initialization with multiple variables (e.g., let i = 0, j = 10)
69
88
  c.tsw.WriteLiterally("let ")
70
89
 
@@ -121,6 +140,64 @@ func (c *GoToTSCompiler) WriteStmtForInit(stmt ast.Stmt) error {
121
140
  }
122
141
  }
123
142
 
143
+ // writeDestructuringValue writes a value expression in a destructuring context.
144
+ // For map index expressions, it generates the full tuple without [0] indexing.
145
+ func (c *GoToTSCompiler) writeDestructuringValue(expr ast.Expr) error {
146
+ // Check if this is a map index expression that should return a tuple
147
+ if indexExpr, ok := expr.(*ast.IndexExpr); ok {
148
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
149
+ underlyingType := tv.Type.Underlying()
150
+ // Check if it's a map type
151
+ if mapType, isMap := underlyingType.(*types.Map); isMap {
152
+ c.tsw.WriteLiterally("$.mapGet(")
153
+ if err := c.WriteValueExpr(indexExpr.X); err != nil {
154
+ return err
155
+ }
156
+ c.tsw.WriteLiterally(", ")
157
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil {
158
+ return err
159
+ }
160
+ c.tsw.WriteLiterally(", ")
161
+ // Write the zero value as the default value for mapGet
162
+ c.WriteZeroValueForType(mapType.Elem())
163
+ c.tsw.WriteLiterally(")") // Don't add [0] for destructuring
164
+ return nil
165
+ }
166
+ // Check if it's a type parameter constrained to be a map type
167
+ if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
168
+ constraint := typeParam.Constraint()
169
+ if constraint != nil {
170
+ underlying := constraint.Underlying()
171
+ if iface, isInterface := underlying.(*types.Interface); isInterface {
172
+ if hasMapConstraint(iface) {
173
+ c.tsw.WriteLiterally("$.mapGet(")
174
+ if err := c.WriteValueExpr(indexExpr.X); err != nil {
175
+ return err
176
+ }
177
+ c.tsw.WriteLiterally(", ")
178
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil {
179
+ return err
180
+ }
181
+ c.tsw.WriteLiterally(", ")
182
+ // Generate the zero value as the default value for mapGet
183
+ mapValueType := getMapValueTypeFromConstraint(iface)
184
+ if mapValueType != nil {
185
+ c.WriteZeroValueForType(mapValueType)
186
+ } else {
187
+ c.tsw.WriteLiterally("null")
188
+ }
189
+ c.tsw.WriteLiterally(")") // Don't add [0] for destructuring
190
+ return nil
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ // For non-map expressions, use the regular WriteValueExpr
198
+ return c.WriteValueExpr(expr)
199
+ }
200
+
124
201
  // WriteStmtForPost translates the post-iteration part of a Go `for` loop header
125
202
  // (e.g., `i++` or `i, j = i+1, j-1` in `for ...; i++`) into its TypeScript
126
203
  // equivalent.