goscript 0.0.61 → 0.0.63

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 (92) hide show
  1. package/README.md +62 -46
  2. package/compiler/analysis.go +621 -19
  3. package/compiler/analysis_test.go +3 -3
  4. package/compiler/assignment.go +100 -0
  5. package/compiler/builtin_test.go +1 -1
  6. package/compiler/compiler.go +76 -16
  7. package/compiler/compiler_test.go +9 -9
  8. package/compiler/composite-lit.go +29 -8
  9. package/compiler/decl.go +20 -11
  10. package/compiler/expr-call-async.go +26 -1
  11. package/compiler/expr-call-builtins.go +60 -4
  12. package/compiler/expr-call-type-conversion.go +37 -5
  13. package/compiler/expr-call.go +26 -6
  14. package/compiler/expr-selector.go +35 -2
  15. package/compiler/expr-type.go +12 -2
  16. package/compiler/expr.go +61 -0
  17. package/compiler/index.test.ts +3 -1
  18. package/compiler/lit.go +13 -4
  19. package/compiler/spec-struct.go +30 -8
  20. package/compiler/spec-value.go +2 -2
  21. package/compiler/spec.go +23 -4
  22. package/compiler/stmt-assign.go +124 -0
  23. package/compiler/stmt-range.go +2 -2
  24. package/compiler/stmt.go +160 -14
  25. package/compiler/type-info.go +3 -5
  26. package/compiler/type-utils.go +40 -1
  27. package/compiler/type.go +52 -14
  28. package/dist/gs/builtin/builtin.d.ts +8 -1
  29. package/dist/gs/builtin/builtin.js +26 -1
  30. package/dist/gs/builtin/builtin.js.map +1 -1
  31. package/dist/gs/builtin/errors.d.ts +1 -0
  32. package/dist/gs/builtin/errors.js +8 -0
  33. package/dist/gs/builtin/errors.js.map +1 -1
  34. package/dist/gs/builtin/slice.d.ts +5 -4
  35. package/dist/gs/builtin/slice.js +88 -51
  36. package/dist/gs/builtin/slice.js.map +1 -1
  37. package/dist/gs/builtin/type.d.ts +23 -2
  38. package/dist/gs/builtin/type.js +125 -0
  39. package/dist/gs/builtin/type.js.map +1 -1
  40. package/dist/gs/builtin/varRef.d.ts +3 -0
  41. package/dist/gs/builtin/varRef.js +6 -1
  42. package/dist/gs/builtin/varRef.js.map +1 -1
  43. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  44. package/dist/gs/bytes/reader.gs.js +1 -1
  45. package/dist/gs/bytes/reader.gs.js.map +1 -1
  46. package/dist/gs/reflect/index.d.ts +2 -2
  47. package/dist/gs/reflect/index.js +1 -1
  48. package/dist/gs/reflect/index.js.map +1 -1
  49. package/dist/gs/reflect/map.d.ts +3 -2
  50. package/dist/gs/reflect/map.js +37 -3
  51. package/dist/gs/reflect/map.js.map +1 -1
  52. package/dist/gs/reflect/type.d.ts +53 -12
  53. package/dist/gs/reflect/type.js +906 -31
  54. package/dist/gs/reflect/type.js.map +1 -1
  55. package/dist/gs/reflect/types.d.ts +11 -12
  56. package/dist/gs/reflect/types.js +26 -15
  57. package/dist/gs/reflect/types.js.map +1 -1
  58. package/dist/gs/reflect/value.d.ts +4 -4
  59. package/dist/gs/reflect/value.js +8 -2
  60. package/dist/gs/reflect/value.js.map +1 -1
  61. package/dist/gs/slices/slices.d.ts +21 -0
  62. package/dist/gs/slices/slices.js +48 -0
  63. package/dist/gs/slices/slices.js.map +1 -1
  64. package/dist/gs/strconv/atoi.gs.js +20 -2
  65. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  66. package/dist/gs/sync/atomic/type.gs.d.ts +3 -3
  67. package/dist/gs/sync/atomic/type.gs.js +13 -7
  68. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  69. package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
  70. package/dist/gs/unicode/utf8/utf8.js +10 -6
  71. package/dist/gs/unicode/utf8/utf8.js.map +1 -1
  72. package/go.mod +6 -6
  73. package/go.sum +12 -8
  74. package/gs/builtin/builtin.ts +27 -2
  75. package/gs/builtin/errors.ts +12 -0
  76. package/gs/builtin/slice.ts +126 -55
  77. package/gs/builtin/type.ts +159 -2
  78. package/gs/builtin/varRef.ts +8 -2
  79. package/gs/bytes/reader.gs.ts +2 -2
  80. package/gs/math/hypot.gs.test.ts +3 -1
  81. package/gs/math/pow10.gs.test.ts +5 -4
  82. package/gs/reflect/index.ts +3 -2
  83. package/gs/reflect/map.test.ts +7 -6
  84. package/gs/reflect/map.ts +49 -7
  85. package/gs/reflect/type.ts +1150 -57
  86. package/gs/reflect/types.ts +34 -21
  87. package/gs/reflect/value.ts +12 -6
  88. package/gs/slices/slices.ts +55 -0
  89. package/gs/strconv/atoi.gs.ts +18 -2
  90. package/gs/sync/atomic/type.gs.ts +15 -10
  91. package/gs/unicode/utf8/utf8.ts +12 -8
  92. package/package.json +23 -14
@@ -168,6 +168,21 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
168
168
 
169
169
  // Handle single assignment using writeAssignmentCore
170
170
  if len(exp.Lhs) == 1 {
171
+ // Check for type shadowing (e.g., field := field{...})
172
+ // In this case, we need to rename the variable to avoid TypeScript shadowing
173
+ if nodeInfo := c.analysis.NodeData[exp]; nodeInfo != nil && nodeInfo.ShadowingInfo != nil {
174
+ if lhsIdent, ok := exp.Lhs[0].(*ast.Ident); ok && lhsIdent.Name != "_" {
175
+ if renamedVar, hasTypeShadow := nodeInfo.ShadowingInfo.TypeShadowedVars[lhsIdent.Name]; hasTypeShadow {
176
+ if err := c.writeTypeShadowedAssignment(exp, lhsIdent.Name, renamedVar); err != nil {
177
+ return err
178
+ }
179
+ c.writeInlineComment(exp)
180
+ c.tsw.WriteLine("")
181
+ return nil
182
+ }
183
+ }
184
+ }
185
+
171
186
  addDeclaration := exp.Tok == token.DEFINE
172
187
  if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
173
188
  return err
@@ -305,6 +320,59 @@ func (c *GoToTSCompiler) writeMultiVarAssignFromCall(lhs []ast.Expr, callExpr *a
305
320
  }
306
321
 
307
322
  if allNewVars && anyNewVars {
323
+ // Check if any variable needs VarRef - if so, we need a different approach
324
+ anyNeedsVarRef := false
325
+ needsVarRefVars := make([]bool, len(lhs))
326
+ for i, lhsExpr := range lhs {
327
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
328
+ if obj := c.pkg.TypesInfo.Defs[ident]; obj != nil {
329
+ if c.analysis.NeedsVarRef(obj) {
330
+ needsVarRefVars[i] = true
331
+ anyNeedsVarRef = true
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ if anyNeedsVarRef {
338
+ // Use temp variables for destructuring, then wrap in VarRef as needed
339
+ c.tsw.WriteLiterally("let [")
340
+ for i, lhsExpr := range lhs {
341
+ if i != 0 {
342
+ c.tsw.WriteLiterally(", ")
343
+ }
344
+ if ident, ok := lhsExpr.(*ast.Ident); ok {
345
+ if ident.Name == "_" {
346
+ // Empty slot for blank identifier
347
+ } else if needsVarRefVars[i] {
348
+ c.tsw.WriteLiterally("_varref_tmp_")
349
+ c.tsw.WriteLiterally(ident.Name)
350
+ } else {
351
+ c.WriteIdent(ident, false)
352
+ }
353
+ } else {
354
+ c.WriteValueExpr(lhsExpr)
355
+ }
356
+ }
357
+ c.tsw.WriteLiterally("] = ")
358
+ c.WriteValueExpr(callExpr)
359
+ c.tsw.WriteLine("")
360
+
361
+ // Now declare the VarRef-wrapped variables
362
+ for i, lhsExpr := range lhs {
363
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && needsVarRefVars[i] {
364
+ c.tsw.WriteLiterally("let ")
365
+ c.WriteIdent(ident, false)
366
+ c.tsw.WriteLiterally(" = $.varRef(_varref_tmp_")
367
+ c.tsw.WriteLiterally(ident.Name)
368
+ // Add non-null assertion to handle cases where the tuple type includes null
369
+ c.tsw.WriteLiterally("!)")
370
+ c.tsw.WriteLine("")
371
+ }
372
+ }
373
+ return nil
374
+ }
375
+
308
376
  c.tsw.WriteLiterally("let [")
309
377
 
310
378
  for i, lhsExpr := range lhs {
@@ -536,3 +604,59 @@ func (c *GoToTSCompiler) writeMapLookupWithExists(lhs []ast.Expr, indexExpr *ast
536
604
 
537
605
  return nil
538
606
  }
607
+
608
+ // writeTypeShadowedAssignment handles the case where a variable name shadows a type name
609
+ // used in its initialization (e.g., field := field{...}).
610
+ // In TypeScript, `let field = new field({...})` fails because the variable shadows the class
611
+ // before initialization due to the Temporal Dead Zone (TDZ). The TDZ extends from the
612
+ // start of the block scope to the point of initialization, so even capturing the type
613
+ // reference before the `let` declaration doesn't work - they're in the same block.
614
+ //
615
+ // We solve this by renaming the variable to avoid the conflict entirely:
616
+ //
617
+ // let field_ = $.markAsStructValue(new field({...}));
618
+ //
619
+ // Then we need to track that all subsequent references to `field` should use `field_`.
620
+ // This is stored in the analysis NodeInfo.IdentifierMapping.
621
+ func (c *GoToTSCompiler) writeTypeShadowedAssignment(exp *ast.AssignStmt, origName, renamedVar string) error {
622
+ if len(exp.Lhs) != 1 || len(exp.Rhs) != 1 {
623
+ return fmt.Errorf("type shadowing assignment must have exactly 1 LHS and 1 RHS")
624
+ }
625
+
626
+ lhsIdent, ok := exp.Lhs[0].(*ast.Ident)
627
+ if !ok {
628
+ return fmt.Errorf("type shadowing assignment LHS must be an identifier")
629
+ }
630
+
631
+ // Check if this variable needs VarRef
632
+ obj := c.objectOfIdent(lhsIdent)
633
+ needsVarRef := obj != nil && c.analysis.NeedsVarRef(obj)
634
+
635
+ // Store the mapping so that subsequent references to this variable use the renamed version
636
+ if obj != nil {
637
+ c.renamedVars[obj] = renamedVar
638
+ }
639
+
640
+ if needsVarRef {
641
+ // For VarRef'd variables:
642
+ // let field_ = $.varRef($.markAsStructValue(new field({...})))
643
+ c.tsw.WriteLiterally("let ")
644
+ c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
645
+ c.tsw.WriteLiterally(" = $.varRef(")
646
+ if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
647
+ return err
648
+ }
649
+ c.tsw.WriteLiterally(")")
650
+ } else {
651
+ // For non-VarRef variables:
652
+ // let field_ = $.markAsStructValue(new field({...}))
653
+ c.tsw.WriteLiterally("let ")
654
+ c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
655
+ c.tsw.WriteLiterally(" = ")
656
+ if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
657
+ return err
658
+ }
659
+ }
660
+
661
+ return nil
662
+ }
@@ -171,7 +171,7 @@ func (c *GoToTSCompiler) writeStringRange(exp *ast.RangeStmt) error {
171
171
 
172
172
  if exp.Value != nil {
173
173
  if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
174
- c.tsw.WriteLiterally("const ")
174
+ c.tsw.WriteLiterally("let ")
175
175
  c.WriteIdent(ident, false)
176
176
  c.tsw.WriteLiterally(" = _runes[")
177
177
  c.tsw.WriteLiterally(indexVarName)
@@ -236,7 +236,7 @@ func (c *GoToTSCompiler) writeArraySliceWithKeyValue(exp *ast.RangeStmt, indexVa
236
236
  c.tsw.WriteLine("")
237
237
 
238
238
  if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
239
- c.tsw.WriteLiterally("const ")
239
+ c.tsw.WriteLiterally("let ")
240
240
  c.WriteIdent(ident, false)
241
241
  c.tsw.WriteLiterally(" = ")
242
242
  if err := c.writeArraySliceExpression(exp.X, isPointer); err != nil {
package/compiler/stmt.go CHANGED
@@ -167,9 +167,17 @@ func (c *GoToTSCompiler) WriteStmtIncDec(stmt *ast.IncDecStmt) error {
167
167
  func (c *GoToTSCompiler) WriteStmtBranch(stmt *ast.BranchStmt) error {
168
168
  switch stmt.Tok {
169
169
  case token.BREAK:
170
- c.tsw.WriteLine("break") // No semicolon needed
170
+ if stmt.Label != nil {
171
+ c.tsw.WriteLinef("break %s", stmt.Label.Name)
172
+ } else {
173
+ c.tsw.WriteLine("break")
174
+ }
171
175
  case token.CONTINUE:
172
- c.tsw.WriteLine("continue") // No semicolon needed
176
+ if stmt.Label != nil {
177
+ c.tsw.WriteLinef("continue %s", stmt.Label.Name)
178
+ } else {
179
+ c.tsw.WriteLine("continue")
180
+ }
173
181
  case token.GOTO:
174
182
  // TypeScript doesn't support goto, but we can handle it by skipping it
175
183
  // since the labeled statement restructuring should handle the control flow
@@ -368,6 +376,15 @@ func (c *GoToTSCompiler) WriteStmtExpr(exp *ast.ExprStmt) error {
368
376
  return nil
369
377
  }
370
378
 
379
+ // Defensive semicolon: if the expression will start with '(' in TypeScript,
380
+ // prepend a semicolon to prevent JavaScript from treating the previous line
381
+ // as a function call. This happens when:
382
+ // 1. CallExpr where Fun itself will be parenthesized (e.g., (await fn())())
383
+ // 2. Array/slice literals starting with '['
384
+ if c.needsDefensiveSemicolon(exp.X) {
385
+ c.tsw.WriteLiterally(";")
386
+ }
387
+
371
388
  // Handle other expression statements
372
389
  if err := c.WriteValueExpr(exp.X); err != nil { // Expression statement evaluates a value
373
390
  return err
@@ -475,17 +492,10 @@ func (c *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
475
492
  }
476
493
  c.tsw.WriteLiterally(") ")
477
494
 
478
- if err := c.WriteStmt(exp.Body); err != nil {
495
+ if err := c.writeIfBody(exp); err != nil {
479
496
  return err
480
497
  }
481
498
 
482
- if exp.Else != nil {
483
- c.tsw.WriteLiterally(" else ")
484
- if err := c.WriteStmt(exp.Else); err != nil {
485
- return err
486
- }
487
- }
488
-
489
499
  c.tsw.Indent(-1)
490
500
  c.tsw.WriteLine("}")
491
501
  return nil
@@ -498,14 +508,31 @@ func (c *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
498
508
  }
499
509
  c.tsw.WriteLiterally(") ")
500
510
 
501
- if err := c.WriteStmt(exp.Body); err != nil {
511
+ return c.writeIfBody(exp)
512
+ }
513
+
514
+ // writeIfBody writes the if body and optional else clause, handling newline suppression.
515
+ func (c *GoToTSCompiler) writeIfBody(exp *ast.IfStmt) error {
516
+ hasElse := exp.Else != nil
517
+ if err := c.WriteStmtBlock(exp.Body, hasElse); err != nil {
502
518
  return err
503
519
  }
504
520
 
505
- if exp.Else != nil {
521
+ if hasElse {
506
522
  c.tsw.WriteLiterally(" else ")
507
- if err := c.WriteStmt(exp.Else); err != nil {
508
- return err
523
+ switch elseStmt := exp.Else.(type) {
524
+ case *ast.BlockStmt:
525
+ if err := c.WriteStmtBlock(elseStmt, false); err != nil {
526
+ return err
527
+ }
528
+ case *ast.IfStmt:
529
+ if err := c.WriteStmtIf(elseStmt); err != nil {
530
+ return err
531
+ }
532
+ default:
533
+ if err := c.WriteStmt(exp.Else); err != nil {
534
+ return err
535
+ }
509
536
  }
510
537
  }
511
538
 
@@ -574,6 +601,12 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
574
601
  }
575
602
  }
576
603
  }
604
+
605
+ // Special handling for primitive types that implement error interface
606
+ if c.writePrimitiveErrorWrapperIfNeeded(exp, res, i) {
607
+ continue
608
+ }
609
+
577
610
  if err := c.WriteValueExpr(res); err != nil { // Return results are values
578
611
  return err
579
612
  }
@@ -586,6 +619,84 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
586
619
  return nil
587
620
  }
588
621
 
622
+ // writePrimitiveErrorWrapperIfNeeded checks if a return value is a primitive type
623
+ // that implements the error interface, and if so, wraps it with $.wrapPrimitiveError.
624
+ // Returns true if the wrapper was written, false otherwise.
625
+ func (c *GoToTSCompiler) writePrimitiveErrorWrapperIfNeeded(retStmt *ast.ReturnStmt, res ast.Expr, resultIndex int) bool {
626
+ // Get the expected return type for this position
627
+ nodeInfo := c.analysis.NodeData[retStmt]
628
+ if nodeInfo == nil || nodeInfo.EnclosingFuncDecl == nil {
629
+ return false
630
+ }
631
+
632
+ funcDecl := nodeInfo.EnclosingFuncDecl
633
+ if funcDecl.Type.Results == nil {
634
+ return false
635
+ }
636
+
637
+ // Find the expected return type for this result index
638
+ var expectedType types.Type
639
+ resultIdx := 0
640
+ for _, field := range funcDecl.Type.Results.List {
641
+ count := len(field.Names)
642
+ if count == 0 {
643
+ count = 1
644
+ }
645
+ for j := 0; j < count; j++ {
646
+ if resultIdx == resultIndex {
647
+ expectedType = c.pkg.TypesInfo.TypeOf(field.Type)
648
+ break
649
+ }
650
+ resultIdx++
651
+ }
652
+ if expectedType != nil {
653
+ break
654
+ }
655
+ }
656
+
657
+ if expectedType == nil {
658
+ return false
659
+ }
660
+
661
+ // Check if the expected type is the error interface
662
+ if iface, ok := expectedType.Underlying().(*types.Interface); !ok || iface.String() != "interface{Error() string}" {
663
+ return false
664
+ }
665
+
666
+ // Get the actual type of the return expression
667
+ actualType := c.pkg.TypesInfo.TypeOf(res)
668
+ if actualType == nil {
669
+ return false
670
+ }
671
+
672
+ // Check if the actual type is a wrapper type (named type with basic underlying type)
673
+ if !c.isWrapperType(actualType) {
674
+ return false
675
+ }
676
+
677
+ // Check if the actual type has an Error() method
678
+ if !c.typeHasMethods(actualType, "Error") {
679
+ return false
680
+ }
681
+
682
+ // Get the qualified type name for the Error function
683
+ typeName := c.getQualifiedTypeName(actualType)
684
+ if typeName == "" {
685
+ return false
686
+ }
687
+
688
+ // Write: $.wrapPrimitiveError(value, TypeName_Error)
689
+ c.tsw.WriteLiterally("$.wrapPrimitiveError(")
690
+ if err := c.WriteValueExpr(res); err != nil {
691
+ return false
692
+ }
693
+ c.tsw.WriteLiterally(", ")
694
+ c.tsw.WriteLiterally(typeName)
695
+ c.tsw.WriteLiterally("_Error)")
696
+
697
+ return true
698
+ }
699
+
589
700
  // WriteStmtBlock translates a Go block statement (`ast.BlockStmt`), typically
590
701
  // `{ ...stmts... }`, into its TypeScript equivalent, carefully preserving
591
702
  // comments and blank lines to maintain code readability and structure.
@@ -1129,3 +1240,38 @@ func (c *GoToTSCompiler) substituteExprForShadowing(expr ast.Expr, shadowingInfo
1129
1240
  func (c *GoToTSCompiler) isBuiltinFunction(name string) bool {
1130
1241
  return builtinFunctions[name]
1131
1242
  }
1243
+
1244
+ // needsDefensiveSemicolon determines if an expression will generate TypeScript
1245
+ // code starting with '(' or '[', which would require a defensive semicolon to
1246
+ // prevent JavaScript from treating the previous line as a function call.
1247
+ func (c *GoToTSCompiler) needsDefensiveSemicolon(expr ast.Expr) bool {
1248
+ switch e := expr.(type) {
1249
+ case *ast.CallExpr:
1250
+ // Check if the function being called will be parenthesized
1251
+ // This happens when Fun is itself a CallExpr, TypeAssertExpr, or other complex expression
1252
+ switch e.Fun.(type) {
1253
+ case *ast.CallExpr:
1254
+ // (fn())() - needs defensive semicolon
1255
+ return true
1256
+ case *ast.TypeAssertExpr:
1257
+ // (x.(T))() - needs defensive semicolon
1258
+ return true
1259
+ case *ast.IndexExpr:
1260
+ // Could generate (arr[i])() if indexed result is called
1261
+ // But typically doesn't need defensive semicolon as arr[i]() is fine
1262
+ return false
1263
+ case *ast.ParenExpr:
1264
+ // Already parenthesized - needs defensive semicolon
1265
+ return true
1266
+ }
1267
+ case *ast.CompositeLit:
1268
+ // Array/slice literals start with '['
1269
+ if _, isArray := e.Type.(*ast.ArrayType); isArray {
1270
+ return true
1271
+ }
1272
+ case *ast.ParenExpr:
1273
+ // Parenthesized expressions start with '('
1274
+ return true
1275
+ }
1276
+ return false
1277
+ }
@@ -26,11 +26,9 @@ func (c *GoToTSCompiler) writeTypeInfoObject(typ types.Type) {
26
26
  underlying := typ.Underlying()
27
27
  switch t := underlying.(type) {
28
28
  case *types.Basic:
29
- tsTypeName, _ := GoBuiltinToTypescript(t.Name())
30
- if tsTypeName == "" {
31
- tsTypeName = t.Name() // Fallback
32
- }
33
- c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Basic, name: %q }", tsTypeName)
29
+ // Use Go type name (e.g., "int") not TypeScript type name (e.g., "number")
30
+ // The reflect system needs Go type names to correctly determine Kind()
31
+ c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Basic, name: %q }", t.Name())
34
32
  // Note: The original 'case *types.Named:' here for 'underlying' is intentionally omitted.
35
33
  // If typ.Underlying() is *types.Named (e.g. type T1 MyInt; type T2 T1;),
36
34
  // then writeTypeInfoObject(typ.Underlying()) would be called in some contexts,
@@ -25,11 +25,50 @@ func (c *GoToTSCompiler) isRuneSliceType(t types.Type) bool {
25
25
  return false
26
26
  }
27
27
 
28
- // isStringType checks if a type is string
28
+ // isStringType checks if a type is string or could be string (e.g., type parameter)
29
29
  func (c *GoToTSCompiler) isStringType(t types.Type) bool {
30
30
  if basic, isBasic := t.Underlying().(*types.Basic); isBasic {
31
31
  return basic.Kind() == types.String || basic.Kind() == types.UntypedString
32
32
  }
33
+ // Handle type parameters (e.g., Bytes ~[]byte | ~string)
34
+ if typeParam, isTypeParam := t.(*types.TypeParam); isTypeParam {
35
+ constraint := typeParam.Constraint()
36
+ if constraintIface, ok := constraint.(*types.Interface); ok {
37
+ return c.constraintIncludesString(constraintIface)
38
+ }
39
+ }
40
+ return false
41
+ }
42
+
43
+ // constraintIncludesString checks if a type constraint includes string
44
+ func (c *GoToTSCompiler) constraintIncludesString(constraint *types.Interface) bool {
45
+ // Check if the constraint has type terms that include string
46
+ if constraint.IsMethodSet() {
47
+ return false // Pure method interface, no type terms
48
+ }
49
+ // For union constraints like []byte | string, check each term
50
+ for i := 0; i < constraint.NumEmbeddeds(); i++ {
51
+ embedded := constraint.EmbeddedType(i)
52
+ // Check if embedded is a union
53
+ if union, isUnion := embedded.(*types.Union); isUnion {
54
+ for j := 0; j < union.Len(); j++ {
55
+ term := union.Term(j)
56
+ termType := term.Type()
57
+ // Check if term is string or ~string
58
+ if basic, isBasic := termType.Underlying().(*types.Basic); isBasic {
59
+ if basic.Kind() == types.String {
60
+ return true
61
+ }
62
+ }
63
+ }
64
+ }
65
+ // Check direct embedded type
66
+ if basic, isBasic := embedded.Underlying().(*types.Basic); isBasic {
67
+ if basic.Kind() == types.String {
68
+ return true
69
+ }
70
+ }
71
+ }
33
72
  return false
34
73
  }
35
74
 
package/compiler/type.go CHANGED
@@ -21,6 +21,9 @@ const (
21
21
  // GoTypeContextVariadicParam is used when translating types for variadic parameter elements.
22
22
  // This affects how interface{} types are handled (no null prefix).
23
23
  GoTypeContextVariadicParam
24
+ // GoTypeContextGenericTypeArg is used when translating type arguments for generic types.
25
+ // This affects how function types are handled (no | null suffix for func() types).
26
+ GoTypeContextGenericTypeArg
24
27
  )
25
28
 
26
29
  // WriteGoType is the main dispatcher for translating Go types to their TypeScript
@@ -68,7 +71,7 @@ func (c *GoToTSCompiler) WriteGoType(typ types.Type, context GoTypeContext) {
68
71
  c.WriteInterfaceType(t, nil) // No ast.InterfaceType available here
69
72
  }
70
73
  case *types.Signature:
71
- c.WriteSignatureType(t)
74
+ c.WriteSignatureType(t, context)
72
75
  case *types.Struct:
73
76
  c.WriteStructType(t)
74
77
  case *types.Alias:
@@ -249,7 +252,8 @@ func (c *GoToTSCompiler) WriteNamedType(t *types.Named) {
249
252
  typePkg := t.Obj().Pkg()
250
253
  if typePkg != nil && typePkg != c.pkg.Types {
251
254
  // This type is from an imported package, find the import alias
252
- if alias, found := c.resolveImportAlias(typePkg); found {
255
+ alias, found := c.resolveImportAlias(typePkg)
256
+ if found && alias != "" {
253
257
  // Write the qualified name: importAlias.TypeName
254
258
  c.tsw.WriteLiterally(alias)
255
259
  c.tsw.WriteLiterally(".")
@@ -262,7 +266,8 @@ func (c *GoToTSCompiler) WriteNamedType(t *types.Named) {
262
266
  if i > 0 {
263
267
  c.tsw.WriteLiterally(", ")
264
268
  }
265
- c.WriteGoType(t.TypeArgs().At(i), GoTypeContextGeneral)
269
+ // Use GenericTypeArg context to avoid adding | null to function types
270
+ c.WriteGoType(t.TypeArgs().At(i), GoTypeContextGenericTypeArg)
266
271
  }
267
272
  c.tsw.WriteLiterally(">")
268
273
  }
@@ -285,7 +290,8 @@ func (c *GoToTSCompiler) WriteNamedType(t *types.Named) {
285
290
  if i > 0 {
286
291
  c.tsw.WriteLiterally(", ")
287
292
  }
288
- c.WriteGoType(t.TypeArgs().At(i), GoTypeContextGeneral)
293
+ // Use GenericTypeArg context to avoid adding | null to function types
294
+ c.WriteGoType(t.TypeArgs().At(i), GoTypeContextGenericTypeArg)
289
295
  }
290
296
  c.tsw.WriteLiterally(">")
291
297
  }
@@ -392,19 +398,39 @@ func (c *GoToTSCompiler) WriteFuncType(exp *ast.FuncType, isAsync bool) {
392
398
  if isAsync {
393
399
  c.tsw.WriteLiterally("Promise<")
394
400
  }
395
- if len(exp.Results.List) == 1 {
401
+
402
+ // Count total number of return values (each field may have multiple names like "a, b int")
403
+ totalResults := 0
404
+ for _, field := range exp.Results.List {
405
+ count := len(field.Names)
406
+ if count == 0 {
407
+ count = 1 // Unnamed return value
408
+ }
409
+ totalResults += count
410
+ }
411
+
412
+ if totalResults == 1 {
396
413
  // Single return type (named or unnamed)
397
414
  // Use WriteTypeExpr to preserve qualified names like os.FileInfo
398
415
  c.WriteTypeExpr(exp.Results.List[0].Type)
399
416
  } else {
400
417
  // Multiple return types -> tuple
401
418
  c.tsw.WriteLiterally("[")
402
- for i, field := range exp.Results.List {
403
- if i > 0 {
404
- c.tsw.WriteLiterally(", ")
419
+ first := true
420
+ for _, field := range exp.Results.List {
421
+ // Each field may represent multiple return values (e.g., "a, b int")
422
+ count := len(field.Names)
423
+ if count == 0 {
424
+ count = 1 // Unnamed return value
425
+ }
426
+ for j := 0; j < count; j++ {
427
+ if !first {
428
+ c.tsw.WriteLiterally(", ")
429
+ }
430
+ first = false
431
+ // Use WriteTypeExpr to preserve qualified names like os.FileInfo
432
+ c.WriteTypeExpr(field.Type)
405
433
  }
406
- // Use WriteTypeExpr to preserve qualified names like os.FileInfo
407
- c.WriteTypeExpr(field.Type)
408
434
  }
409
435
  c.tsw.WriteLiterally("]")
410
436
  }
@@ -440,7 +466,11 @@ func (c *GoToTSCompiler) WriteInterfaceType(t *types.Interface, astNode *ast.Int
440
466
 
441
467
  // WriteSignatureType translates a Go function signature to its TypeScript equivalent.
442
468
  // It generates (param1: type1, param2: type2, ...): returnType for function types.
443
- func (c *GoToTSCompiler) WriteSignatureType(t *types.Signature) {
469
+ // The context parameter determines whether to add | null suffix:
470
+ // - GoTypeContextGeneral: Add | null (function values can be nil in Go)
471
+ // - GoTypeContextFunctionReturn: Add | null (function return values can be nil)
472
+ // - Other contexts (like type arguments): Don't add | null
473
+ func (c *GoToTSCompiler) WriteSignatureType(t *types.Signature, context GoTypeContext) {
444
474
  c.tsw.WriteLiterally("(")
445
475
  c.tsw.WriteLiterally("(")
446
476
  params := t.Params()
@@ -492,7 +522,15 @@ func (c *GoToTSCompiler) WriteSignatureType(t *types.Signature) {
492
522
  }
493
523
  c.tsw.WriteLiterally("]")
494
524
  }
495
- c.tsw.WriteLiterally(") | null")
525
+ c.tsw.WriteLiterally(")")
526
+
527
+ // In Go, function values (not pointers to functions) can be nil.
528
+ // Add | null for general contexts and function return contexts.
529
+ // Don't add | null for other contexts like type arguments to avoid
530
+ // issues with generic types like atomic.Pointer[func()].
531
+ if context == GoTypeContextGeneral || context == GoTypeContextFunctionReturn {
532
+ c.tsw.WriteLiterally(" | null")
533
+ }
496
534
  }
497
535
 
498
536
  // writeInterfaceStructure translates a Go `types.Interface` into its TypeScript structural representation.
@@ -680,7 +718,7 @@ func (c *GoToTSCompiler) writeInterfaceStructure(iface *types.Interface, astNode
680
718
  func (c *GoToTSCompiler) getTypeString(goType types.Type) string {
681
719
  var typeStr strings.Builder
682
720
  writer := NewTSCodeWriter(&typeStr)
683
- tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis)
721
+ tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis, c.currentFilePath)
684
722
  tempCompiler.WriteGoType(goType, GoTypeContextGeneral)
685
723
  return typeStr.String()
686
724
  }
@@ -692,7 +730,7 @@ func (c *GoToTSCompiler) getTypeString(goType types.Type) string {
692
730
  func (c *GoToTSCompiler) getASTTypeString(astType ast.Expr, goType types.Type) string {
693
731
  var typeStr strings.Builder
694
732
  writer := NewTSCodeWriter(&typeStr)
695
- tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis)
733
+ tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis, c.currentFilePath)
696
734
 
697
735
  if astType != nil {
698
736
  // Use AST-based type writing to preserve qualified names
@@ -8,7 +8,14 @@ export declare function println(...args: any[]): void;
8
8
  * Implementation of Go's built-in panic function
9
9
  * @param args Arguments passed to panic
10
10
  */
11
- export declare function panic(...args: any[]): void;
11
+ export declare function panic(...args: any[]): never;
12
+ /**
13
+ * Implementation of Go's built-in clear function.
14
+ * For slices, it sets all elements to their zero value.
15
+ * For maps, it deletes all entries.
16
+ * @param v The slice or map to clear
17
+ */
18
+ export declare function clear<T>(v: T[] | Map<unknown, unknown> | null): void;
12
19
  export type Bytes = Uint8Array | Slice<number>;
13
20
  export declare function int(value: number): number;
14
21
  /**
@@ -4,7 +4,13 @@ import { isSliceProxy } from './slice.js';
4
4
  * @param args Arguments to print
5
5
  */
6
6
  export function println(...args) {
7
- console.log(...args);
7
+ if (args.length === 0) {
8
+ // Bun's console.log() with no args doesn't print a newline, so we explicitly print an empty string
9
+ console.log('');
10
+ }
11
+ else {
12
+ console.log(...args);
13
+ }
8
14
  }
9
15
  /**
10
16
  * Implementation of Go's built-in panic function
@@ -13,6 +19,25 @@ export function println(...args) {
13
19
  export function panic(...args) {
14
20
  throw new Error(`panic: ${args.map((arg) => String(arg)).join(' ')}`);
15
21
  }
22
+ /**
23
+ * Implementation of Go's built-in clear function.
24
+ * For slices, it sets all elements to their zero value.
25
+ * For maps, it deletes all entries.
26
+ * @param v The slice or map to clear
27
+ */
28
+ export function clear(v) {
29
+ if (v === null || v === undefined) {
30
+ return;
31
+ }
32
+ if (v instanceof Map) {
33
+ v.clear();
34
+ return;
35
+ }
36
+ if (Array.isArray(v)) {
37
+ v.fill(null);
38
+ return;
39
+ }
40
+ }
16
41
  // int converts a value to a Go int type, handling proper signed integer conversion
17
42
  // This ensures that values like 2147483648 (2^31) are properly handled according to Go semantics
18
43
  export function int(value) {