goscript 0.0.60 → 0.0.62

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 (87) hide show
  1. package/README.md +9 -0
  2. package/compiler/analysis.go +974 -369
  3. package/compiler/assignment.go +72 -0
  4. package/compiler/compiler.go +74 -15
  5. package/compiler/composite-lit.go +29 -8
  6. package/compiler/decl.go +67 -98
  7. package/compiler/expr-call-async.go +26 -1
  8. package/compiler/expr-call-builtins.go +60 -4
  9. package/compiler/expr-call-helpers.go +182 -0
  10. package/compiler/expr-call-type-conversion.go +37 -5
  11. package/compiler/expr-call.go +25 -33
  12. package/compiler/expr-selector.go +71 -1
  13. package/compiler/expr-type.go +49 -3
  14. package/compiler/expr.go +37 -28
  15. package/compiler/index.test.ts +3 -1
  16. package/compiler/lit.go +13 -4
  17. package/compiler/spec-struct.go +42 -9
  18. package/compiler/spec-value.go +2 -2
  19. package/compiler/spec.go +42 -5
  20. package/compiler/stmt-assign.go +71 -0
  21. package/compiler/stmt-range.go +2 -2
  22. package/compiler/stmt.go +130 -10
  23. package/compiler/type-utils.go +40 -16
  24. package/compiler/type.go +50 -12
  25. package/dist/gs/builtin/builtin.d.ts +8 -1
  26. package/dist/gs/builtin/builtin.js +26 -1
  27. package/dist/gs/builtin/builtin.js.map +1 -1
  28. package/dist/gs/builtin/errors.d.ts +1 -0
  29. package/dist/gs/builtin/errors.js +8 -0
  30. package/dist/gs/builtin/errors.js.map +1 -1
  31. package/dist/gs/builtin/slice.d.ts +5 -4
  32. package/dist/gs/builtin/slice.js +51 -21
  33. package/dist/gs/builtin/slice.js.map +1 -1
  34. package/dist/gs/builtin/type.d.ts +28 -2
  35. package/dist/gs/builtin/type.js +132 -0
  36. package/dist/gs/builtin/type.js.map +1 -1
  37. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  38. package/dist/gs/bytes/reader.gs.js +1 -1
  39. package/dist/gs/bytes/reader.gs.js.map +1 -1
  40. package/dist/gs/internal/byteorder/index.d.ts +6 -0
  41. package/dist/gs/internal/byteorder/index.js +34 -0
  42. package/dist/gs/internal/byteorder/index.js.map +1 -1
  43. package/dist/gs/reflect/index.d.ts +3 -3
  44. package/dist/gs/reflect/index.js +2 -2
  45. package/dist/gs/reflect/index.js.map +1 -1
  46. package/dist/gs/reflect/map.d.ts +3 -2
  47. package/dist/gs/reflect/map.js +37 -3
  48. package/dist/gs/reflect/map.js.map +1 -1
  49. package/dist/gs/reflect/type.d.ts +55 -8
  50. package/dist/gs/reflect/type.js +889 -23
  51. package/dist/gs/reflect/type.js.map +1 -1
  52. package/dist/gs/reflect/types.d.ts +11 -12
  53. package/dist/gs/reflect/types.js +26 -15
  54. package/dist/gs/reflect/types.js.map +1 -1
  55. package/dist/gs/reflect/value.d.ts +4 -4
  56. package/dist/gs/reflect/value.js +8 -2
  57. package/dist/gs/reflect/value.js.map +1 -1
  58. package/dist/gs/slices/slices.d.ts +32 -0
  59. package/dist/gs/slices/slices.js +81 -0
  60. package/dist/gs/slices/slices.js.map +1 -1
  61. package/dist/gs/sync/atomic/type.gs.d.ts +2 -2
  62. package/dist/gs/sync/atomic/type.gs.js +12 -2
  63. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  64. package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
  65. package/dist/gs/unicode/utf8/utf8.js +10 -6
  66. package/dist/gs/unicode/utf8/utf8.js.map +1 -1
  67. package/go.mod +4 -4
  68. package/go.sum +8 -16
  69. package/gs/builtin/builtin.ts +27 -2
  70. package/gs/builtin/errors.ts +12 -0
  71. package/gs/builtin/slice.ts +77 -14
  72. package/gs/builtin/type.ts +167 -2
  73. package/gs/bytes/reader.gs.ts +2 -2
  74. package/gs/internal/byteorder/index.ts +40 -0
  75. package/gs/math/hypot.gs.test.ts +3 -1
  76. package/gs/math/pow10.gs.test.ts +5 -4
  77. package/gs/reflect/index.ts +6 -3
  78. package/gs/reflect/map.test.ts +7 -6
  79. package/gs/reflect/map.ts +49 -7
  80. package/gs/reflect/type.ts +1139 -43
  81. package/gs/reflect/types.ts +34 -21
  82. package/gs/reflect/value.ts +12 -6
  83. package/gs/slices/slices.ts +92 -0
  84. package/gs/sync/atomic/type.gs.ts +14 -5
  85. package/gs/sync/meta.json +1 -1
  86. package/gs/unicode/utf8/utf8.ts +12 -8
  87. package/package.json +13 -13
package/compiler/expr.go CHANGED
@@ -251,34 +251,6 @@ func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
251
251
  return "unknown"
252
252
  }
253
253
 
254
- // getFinalUnderlyingType traverses the chain of named types to find the ultimate underlying type.
255
- // This handles cases like: type A B; type B C; type C string.
256
- // Returns the final underlying type and whether the original type was a named type.
257
- func (c *GoToTSCompiler) getFinalUnderlyingType(t types.Type) (types.Type, bool) {
258
- if t == nil {
259
- return nil, false
260
- }
261
-
262
- // Check if this is a named type
263
- namedType, isNamed := t.(*types.Named)
264
- if !isNamed {
265
- return t, false
266
- }
267
-
268
- // Follow the chain of named types to find the ultimate underlying type
269
- ultimate := namedType
270
- for {
271
- underlying := ultimate.Underlying()
272
- if underlyingNamed, isNamedUnderlying := underlying.(*types.Named); isNamedUnderlying {
273
- // Continue following the chain
274
- ultimate = underlyingNamed
275
- } else {
276
- // We've reached the final underlying type
277
- return underlying, true
278
- }
279
- }
280
- }
281
-
282
254
  // WriteBinaryExpr translates a Go binary expression (`ast.BinaryExpr`) into its
283
255
  // TypeScript equivalent.
284
256
  // It handles several cases:
@@ -469,9 +441,46 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
469
441
  c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
470
442
  }
471
443
 
444
+ // Check if this is a comparison that might trigger TypeScript's control flow narrowing.
445
+ // When a numeric field is compared to a literal, TypeScript may narrow the field type
446
+ // to that literal, causing subsequent comparisons to appear unintentional.
447
+ // We cast to number to widen the type and avoid these false positives.
448
+ needsNumberCast := false
449
+ if (exp.Op == token.EQL || exp.Op == token.NEQ) && c.pkg != nil && c.pkg.TypesInfo != nil {
450
+ // Check if left side is a selector expression (field access) and right is a literal or constant
451
+ _, leftIsSelector := exp.X.(*ast.SelectorExpr)
452
+ _, rightIsLiteral := exp.Y.(*ast.BasicLit)
453
+ // Also check if right side is an identifier referring to a constant
454
+ rightIsConstant := false
455
+ if rightIdent, ok := exp.Y.(*ast.Ident); ok {
456
+ if obj := c.pkg.TypesInfo.Uses[rightIdent]; obj != nil {
457
+ if _, isConst := obj.(*types.Const); isConst {
458
+ rightIsConstant = true
459
+ }
460
+ }
461
+ }
462
+ if leftIsSelector && (rightIsLiteral || rightIsConstant) {
463
+ leftType := c.pkg.TypesInfo.TypeOf(exp.X)
464
+ if leftType != nil {
465
+ if basic, ok := leftType.Underlying().(*types.Basic); ok {
466
+ // Check if it's a numeric type
467
+ if basic.Info()&types.IsNumeric != 0 {
468
+ needsNumberCast = true
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+
475
+ if needsNumberCast {
476
+ c.tsw.WriteLiterally("Number(")
477
+ }
472
478
  if err := c.WriteValueExpr(exp.X); err != nil {
473
479
  return fmt.Errorf("failed to write binary expression left operand: %w", err)
474
480
  }
481
+ if needsNumberCast {
482
+ c.tsw.WriteLiterally(")")
483
+ }
475
484
 
476
485
  c.tsw.WriteLiterally(" ")
477
486
  tokStr, ok := TokenToTs(exp.Op)
@@ -24,6 +24,8 @@ describe("GoScript Compiler API", () => {
24
24
  };
25
25
 
26
26
  await expect(compile(config)).resolves.toBeUndefined();
27
- await expect(fs.access(expectedOutputFile)).resolves.toBeUndefined();
27
+ // fs.access resolves to undefined/null on success - verify file exists
28
+ const fileExists = await fs.access(expectedOutputFile).then(() => true).catch(() => false);
29
+ expect(fileExists).toBe(true);
28
30
  }, 30000); // 30 second timeout for compilation
29
31
  });
package/compiler/lit.go CHANGED
@@ -143,11 +143,20 @@ func (c *GoToTSCompiler) WriteFuncLitValue(exp *ast.FuncLit) error {
143
143
  c.WriteTypeExpr(exp.Type.Results.List[0].Type)
144
144
  } else {
145
145
  c.tsw.WriteLiterally("[")
146
- for i, field := range exp.Type.Results.List {
147
- if i > 0 {
148
- c.tsw.WriteLiterally(", ")
146
+ first := true
147
+ for _, field := range exp.Type.Results.List {
148
+ // Each field may represent multiple return values (e.g., "a, b int")
149
+ count := len(field.Names)
150
+ if count == 0 {
151
+ count = 1 // Unnamed return value
152
+ }
153
+ for j := 0; j < count; j++ {
154
+ if !first {
155
+ c.tsw.WriteLiterally(", ")
156
+ }
157
+ first = false
158
+ c.WriteTypeExpr(field.Type)
149
159
  }
150
- c.WriteTypeExpr(field.Type)
151
160
  }
152
161
  c.tsw.WriteLiterally("]")
153
162
  }
@@ -4,7 +4,7 @@ import (
4
4
  "fmt"
5
5
  "go/ast"
6
6
  "go/types"
7
- "sort"
7
+ "slices"
8
8
  "strings"
9
9
  )
10
10
 
@@ -109,8 +109,10 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
109
109
  }
110
110
 
111
111
  // Use AST-based type string when available, fall back to types-based
112
+ // Note: getASTTypeString already includes "null |" for interface types
112
113
  astType := fieldASTTypes[fieldKeyName]
113
114
  fieldTsType := c.getASTTypeString(astType, field.Type())
115
+
114
116
  c.tsw.WriteLinef("%s: $.VarRef<%s>;", fieldKeyName, fieldTsType)
115
117
  }
116
118
  c.tsw.Indent(-1)
@@ -346,7 +348,16 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
346
348
  }
347
349
 
348
350
  // Promoted methods
349
- embeddedMethodSet := types.NewMethodSet(embeddedFieldType) // Use original field type for method set
351
+ // Use pointer to embedded type to get both value and pointer receiver methods
352
+ // This matches Go's behavior where embedding T promotes both T and *T methods
353
+ // Exception: For interfaces, use the interface directly as pointer-to-interface has no methods
354
+ methodSetType := embeddedFieldType
355
+ if _, isPtr := embeddedFieldType.(*types.Pointer); !isPtr {
356
+ if _, isInterface := embeddedFieldType.Underlying().(*types.Interface); !isInterface {
357
+ methodSetType = types.NewPointer(embeddedFieldType)
358
+ }
359
+ }
360
+ embeddedMethodSet := types.NewMethodSet(methodSetType)
350
361
  for k := range embeddedMethodSet.Len() {
351
362
  methodSelection := embeddedMethodSet.At(k)
352
363
  method := methodSelection.Obj().(*types.Func)
@@ -429,8 +440,19 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
429
440
  // Add code to register the type with the runtime type system
430
441
  c.tsw.WriteLine("")
431
442
  c.tsw.WriteLinef("// Register this type with the runtime type system")
443
+
444
+ // Build full package path name for registration
445
+ structName := className
446
+ pkgPath := c.pkg.Types.Path()
447
+ pkgName := c.pkg.Types.Name()
448
+ if pkgPath != "" && pkgName != "main" {
449
+ structName = pkgPath + "." + className
450
+ } else if pkgName == "main" {
451
+ structName = "main." + className
452
+ }
453
+
432
454
  c.tsw.WriteLinef("static __typeInfo = $.registerStructType(")
433
- c.tsw.WriteLinef(" '%s',", className)
455
+ c.tsw.WriteLinef(" '%s',", structName)
434
456
  c.tsw.WriteLinef(" new %s(),", className)
435
457
  c.tsw.WriteLiterally(" [")
436
458
  // Collect methods for the struct type
@@ -468,13 +490,23 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
468
490
  if fieldKeyName == "_" {
469
491
  continue
470
492
  }
471
- // fieldTsType := c.getTypeString(field.Type())
472
493
  if !firstField {
473
494
  c.tsw.WriteLiterally(", ")
474
495
  }
475
496
  firstField = false
476
497
  c.tsw.WriteLiterallyf("%q: ", fieldKeyName)
477
- c.writeTypeInfoObject(field.Type()) // Use writeTypeInfoObject for field types
498
+
499
+ // Get the struct tag for this field
500
+ tag := underlyingStruct.Tag(i)
501
+ if tag != "" {
502
+ // Write field info with tag as StructFieldInfo object
503
+ c.tsw.WriteLiterally("{ type: ")
504
+ c.writeTypeInfoObject(field.Type())
505
+ c.tsw.WriteLiterallyf(", tag: %q }", tag)
506
+ } else {
507
+ // No tag, write type info directly for backwards compatibility
508
+ c.writeTypeInfoObject(field.Type())
509
+ }
478
510
  }
479
511
  c.tsw.WriteLiterally("}")
480
512
  c.tsw.WriteLine("")
@@ -532,15 +564,16 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
532
564
  fieldType = ptr.Elem()
533
565
  }
534
566
 
535
- if named, ok := fieldType.(*types.Named); ok {
536
- embeddedName := named.Obj().Name()
567
+ if _, ok := fieldType.(*types.Named); ok {
537
568
  // Check if the embedded type is an interface
538
569
  if _, isInterface := fieldType.Underlying().(*types.Interface); isInterface {
539
570
  // For embedded interfaces, use the full qualified interface type
540
571
  embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = c.getTypeString(field.Type())
541
572
  } else {
542
573
  // For embedded structs, use ConstructorParameters for field-based initialization
543
- embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = fmt.Sprintf("Partial<ConstructorParameters<typeof %s>[0]>", embeddedName)
574
+ // Use getTypeString to get the qualified type name (e.g., bytes.Buffer not just Buffer)
575
+ qualifiedTypeName := c.getTypeString(fieldType)
576
+ embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = fmt.Sprintf("Partial<ConstructorParameters<typeof %s>[0]>", qualifiedTypeName)
544
577
  }
545
578
  }
546
579
  continue
@@ -562,7 +595,7 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
562
595
  for name := range fieldMap {
563
596
  fieldNames = append(fieldNames, name)
564
597
  }
565
- sort.Strings(fieldNames)
598
+ slices.Sort(fieldNames)
566
599
 
567
600
  var fieldDefs []string
568
601
  for _, fieldName := range fieldNames {
@@ -164,7 +164,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
164
164
  isInsideFunction = nodeInfo.IsInsideFunction
165
165
  }
166
166
 
167
- if name.IsExported() && !isInsideFunction {
167
+ if !isInsideFunction {
168
168
  c.tsw.WriteLiterally("export ")
169
169
  }
170
170
  c.tsw.WriteLiterally("let ")
@@ -493,7 +493,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
493
493
  isInsideFunction = nodeInfo.IsInsideFunction
494
494
  }
495
495
 
496
- if name.IsExported() && !isInsideFunction {
496
+ if !isInsideFunction {
497
497
  c.tsw.WriteLiterally("export ")
498
498
  }
499
499
 
package/compiler/spec.go CHANGED
@@ -57,6 +57,7 @@ func (c *GoToTSCompiler) getEmbeddedFieldKeyName(fieldType types.Type) string {
57
57
 
58
58
  func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Type, doc, comment *ast.CommentGroup, astType ast.Expr) {
59
59
  // Use AST type information if available to preserve qualified names
60
+ // Note: getASTTypeString/getTypeString already includes "null |" for interface types
60
61
  var fieldTypeStr string
61
62
  if astType != nil {
62
63
  fieldTypeStr = c.getASTTypeString(astType, fieldType)
@@ -121,7 +122,7 @@ func (c *GoToTSCompiler) writeEmbeddedFieldInitializer(fieldName string, fieldTy
121
122
  }
122
123
 
123
124
  // For structs, instantiate with provided fields
124
- typeForNew := fieldName
125
+ typeForNew := c.getTypeString(fieldType)
125
126
  c.tsw.WriteLiterallyf("new %s(init?.%s)", typeForNew, fieldName)
126
127
  }
127
128
 
@@ -224,6 +225,12 @@ func (c *GoToTSCompiler) writeNamedTypeZeroValue(named *types.Named) {
224
225
  return
225
226
  }
226
227
 
228
+ // Check if underlying type is a function
229
+ if _, isFunc := named.Underlying().(*types.Signature); isFunc {
230
+ c.tsw.WriteLiterally("null")
231
+ return
232
+ }
233
+
227
234
  // For non-struct, non-interface named types, use constructor
228
235
  c.tsw.WriteLiterally("new ")
229
236
  c.WriteNamedType(named)
@@ -254,6 +261,12 @@ func (c *GoToTSCompiler) writeTypeAliasZeroValue(alias *types.Alias, astType ast
254
261
  return
255
262
  }
256
263
 
264
+ // Check if underlying type is a function
265
+ if _, isFunc := alias.Underlying().(*types.Signature); isFunc {
266
+ c.tsw.WriteLiterally("null")
267
+ return
268
+ }
269
+
257
270
  // For non-struct, non-interface type aliases, use constructor
258
271
  c.tsw.WriteLiterally("new ")
259
272
  // Use AST type information if available to preserve qualified names
@@ -394,11 +407,20 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
394
407
  c.WriteTypeExpr(funcDecl.Type.Results.List[0].Type)
395
408
  } else {
396
409
  c.tsw.WriteLiterally("[")
397
- for i, field := range funcDecl.Type.Results.List {
398
- if i > 0 {
399
- c.tsw.WriteLiterally(", ")
410
+ first := true
411
+ for _, field := range funcDecl.Type.Results.List {
412
+ // Each field may represent multiple return values (e.g., "a, b int")
413
+ count := len(field.Names)
414
+ if count == 0 {
415
+ count = 1 // Unnamed return value
416
+ }
417
+ for j := 0; j < count; j++ {
418
+ if !first {
419
+ c.tsw.WriteLiterally(", ")
420
+ }
421
+ first = false
422
+ c.WriteTypeExpr(field.Type)
400
423
  }
401
- c.WriteTypeExpr(field.Type)
402
424
  }
403
425
  c.tsw.WriteLiterally("]")
404
426
  }
@@ -521,7 +543,15 @@ func (c *GoToTSCompiler) WriteInterfaceTypeSpec(a *ast.TypeSpec, t *ast.Interfac
521
543
  c.tsw.WriteLine("")
522
544
 
523
545
  // Add code to register the interface with the runtime system
546
+ // Build full package path name for registration
524
547
  interfaceName := a.Name.Name
548
+ pkgPath := c.pkg.Types.Path()
549
+ pkgName := c.pkg.Types.Name()
550
+ if pkgPath != "" && pkgName != "main" {
551
+ interfaceName = pkgPath + "." + interfaceName
552
+ } else if pkgName == "main" {
553
+ interfaceName = "main." + interfaceName
554
+ }
525
555
  c.tsw.WriteLine("")
526
556
  c.tsw.WriteLinef("$.registerInterfaceType(")
527
557
  c.tsw.WriteLinef(" '%s',", interfaceName)
@@ -605,6 +635,13 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
605
635
  importVars: make(map[string]struct{}),
606
636
  }
607
637
 
638
+ // Skip writing the import if it was already written as a synthetic import
639
+ // This prevents duplicate imports when a file needs an import both from
640
+ // its AST and for promoted methods from embedded structs
641
+ if _, isSynthetic := c.analysis.SyntheticImports[impName]; isSynthetic {
642
+ return
643
+ }
644
+
608
645
  c.tsw.WriteImport(impName, tsImportPath+"/index.js")
609
646
  }
610
647
 
@@ -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
@@ -536,3 +551,59 @@ func (c *GoToTSCompiler) writeMapLookupWithExists(lhs []ast.Expr, indexExpr *ast
536
551
 
537
552
  return nil
538
553
  }
554
+
555
+ // writeTypeShadowedAssignment handles the case where a variable name shadows a type name
556
+ // used in its initialization (e.g., field := field{...}).
557
+ // In TypeScript, `let field = new field({...})` fails because the variable shadows the class
558
+ // before initialization due to the Temporal Dead Zone (TDZ). The TDZ extends from the
559
+ // start of the block scope to the point of initialization, so even capturing the type
560
+ // reference before the `let` declaration doesn't work - they're in the same block.
561
+ //
562
+ // We solve this by renaming the variable to avoid the conflict entirely:
563
+ //
564
+ // let field_ = $.markAsStructValue(new field({...}));
565
+ //
566
+ // Then we need to track that all subsequent references to `field` should use `field_`.
567
+ // This is stored in the analysis NodeInfo.IdentifierMapping.
568
+ func (c *GoToTSCompiler) writeTypeShadowedAssignment(exp *ast.AssignStmt, origName, renamedVar string) error {
569
+ if len(exp.Lhs) != 1 || len(exp.Rhs) != 1 {
570
+ return fmt.Errorf("type shadowing assignment must have exactly 1 LHS and 1 RHS")
571
+ }
572
+
573
+ lhsIdent, ok := exp.Lhs[0].(*ast.Ident)
574
+ if !ok {
575
+ return fmt.Errorf("type shadowing assignment LHS must be an identifier")
576
+ }
577
+
578
+ // Check if this variable needs VarRef
579
+ obj := c.objectOfIdent(lhsIdent)
580
+ needsVarRef := obj != nil && c.analysis.NeedsVarRef(obj)
581
+
582
+ // Store the mapping so that subsequent references to this variable use the renamed version
583
+ if obj != nil {
584
+ c.renamedVars[obj] = renamedVar
585
+ }
586
+
587
+ if needsVarRef {
588
+ // For VarRef'd variables:
589
+ // let field_ = $.varRef($.markAsStructValue(new field({...})))
590
+ c.tsw.WriteLiterally("let ")
591
+ c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
592
+ c.tsw.WriteLiterally(" = $.varRef(")
593
+ if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
594
+ return err
595
+ }
596
+ c.tsw.WriteLiterally(")")
597
+ } else {
598
+ // For non-VarRef variables:
599
+ // let field_ = $.markAsStructValue(new field({...}))
600
+ c.tsw.WriteLiterally("let ")
601
+ c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
602
+ c.tsw.WriteLiterally(" = ")
603
+ if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
604
+ return err
605
+ }
606
+ }
607
+
608
+ return nil
609
+ }
@@ -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
@@ -368,6 +368,15 @@ func (c *GoToTSCompiler) WriteStmtExpr(exp *ast.ExprStmt) error {
368
368
  return nil
369
369
  }
370
370
 
371
+ // Defensive semicolon: if the expression will start with '(' in TypeScript,
372
+ // prepend a semicolon to prevent JavaScript from treating the previous line
373
+ // as a function call. This happens when:
374
+ // 1. CallExpr where Fun itself will be parenthesized (e.g., (await fn())())
375
+ // 2. Array/slice literals starting with '['
376
+ if c.needsDefensiveSemicolon(exp.X) {
377
+ c.tsw.WriteLiterally(";")
378
+ }
379
+
371
380
  // Handle other expression statements
372
381
  if err := c.WriteValueExpr(exp.X); err != nil { // Expression statement evaluates a value
373
382
  return err
@@ -528,16 +537,8 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
528
537
  nodeInfo := c.analysis.NodeData[exp]
529
538
  if nodeInfo != nil && nodeInfo.IsBareReturn {
530
539
  var namedReturns []string
531
- if nodeInfo.EnclosingFuncDecl != nil {
532
- if obj := c.pkg.TypesInfo.ObjectOf(nodeInfo.EnclosingFuncDecl.Name); obj != nil {
533
- if funcInfo := c.analysis.FunctionData[obj]; funcInfo != nil {
534
- namedReturns = funcInfo.NamedReturns
535
- }
536
- }
537
- } else if nodeInfo.EnclosingFuncLit != nil {
538
- if funcInfo := c.analysis.FuncLitData[nodeInfo.EnclosingFuncLit]; funcInfo != nil {
539
- namedReturns = funcInfo.NamedReturns
540
- }
540
+ if funcInfo := c.analysis.GetFunctionInfoFromContext(nodeInfo, c.pkg); funcInfo != nil {
541
+ namedReturns = funcInfo.NamedReturns
541
542
  }
542
543
 
543
544
  if len(namedReturns) == 1 {
@@ -582,6 +583,12 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
582
583
  }
583
584
  }
584
585
  }
586
+
587
+ // Special handling for primitive types that implement error interface
588
+ if c.writePrimitiveErrorWrapperIfNeeded(exp, res, i) {
589
+ continue
590
+ }
591
+
585
592
  if err := c.WriteValueExpr(res); err != nil { // Return results are values
586
593
  return err
587
594
  }
@@ -594,6 +601,84 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
594
601
  return nil
595
602
  }
596
603
 
604
+ // writePrimitiveErrorWrapperIfNeeded checks if a return value is a primitive type
605
+ // that implements the error interface, and if so, wraps it with $.wrapPrimitiveError.
606
+ // Returns true if the wrapper was written, false otherwise.
607
+ func (c *GoToTSCompiler) writePrimitiveErrorWrapperIfNeeded(retStmt *ast.ReturnStmt, res ast.Expr, resultIndex int) bool {
608
+ // Get the expected return type for this position
609
+ nodeInfo := c.analysis.NodeData[retStmt]
610
+ if nodeInfo == nil || nodeInfo.EnclosingFuncDecl == nil {
611
+ return false
612
+ }
613
+
614
+ funcDecl := nodeInfo.EnclosingFuncDecl
615
+ if funcDecl.Type.Results == nil {
616
+ return false
617
+ }
618
+
619
+ // Find the expected return type for this result index
620
+ var expectedType types.Type
621
+ resultIdx := 0
622
+ for _, field := range funcDecl.Type.Results.List {
623
+ count := len(field.Names)
624
+ if count == 0 {
625
+ count = 1
626
+ }
627
+ for j := 0; j < count; j++ {
628
+ if resultIdx == resultIndex {
629
+ expectedType = c.pkg.TypesInfo.TypeOf(field.Type)
630
+ break
631
+ }
632
+ resultIdx++
633
+ }
634
+ if expectedType != nil {
635
+ break
636
+ }
637
+ }
638
+
639
+ if expectedType == nil {
640
+ return false
641
+ }
642
+
643
+ // Check if the expected type is the error interface
644
+ if iface, ok := expectedType.Underlying().(*types.Interface); !ok || iface.String() != "interface{Error() string}" {
645
+ return false
646
+ }
647
+
648
+ // Get the actual type of the return expression
649
+ actualType := c.pkg.TypesInfo.TypeOf(res)
650
+ if actualType == nil {
651
+ return false
652
+ }
653
+
654
+ // Check if the actual type is a wrapper type (named type with basic underlying type)
655
+ if !c.isWrapperType(actualType) {
656
+ return false
657
+ }
658
+
659
+ // Check if the actual type has an Error() method
660
+ if !c.typeHasMethods(actualType, "Error") {
661
+ return false
662
+ }
663
+
664
+ // Get the qualified type name for the Error function
665
+ typeName := c.getQualifiedTypeName(actualType)
666
+ if typeName == "" {
667
+ return false
668
+ }
669
+
670
+ // Write: $.wrapPrimitiveError(value, TypeName_Error)
671
+ c.tsw.WriteLiterally("$.wrapPrimitiveError(")
672
+ if err := c.WriteValueExpr(res); err != nil {
673
+ return false
674
+ }
675
+ c.tsw.WriteLiterally(", ")
676
+ c.tsw.WriteLiterally(typeName)
677
+ c.tsw.WriteLiterally("_Error)")
678
+
679
+ return true
680
+ }
681
+
597
682
  // WriteStmtBlock translates a Go block statement (`ast.BlockStmt`), typically
598
683
  // `{ ...stmts... }`, into its TypeScript equivalent, carefully preserving
599
684
  // comments and blank lines to maintain code readability and structure.
@@ -1137,3 +1222,38 @@ func (c *GoToTSCompiler) substituteExprForShadowing(expr ast.Expr, shadowingInfo
1137
1222
  func (c *GoToTSCompiler) isBuiltinFunction(name string) bool {
1138
1223
  return builtinFunctions[name]
1139
1224
  }
1225
+
1226
+ // needsDefensiveSemicolon determines if an expression will generate TypeScript
1227
+ // code starting with '(' or '[', which would require a defensive semicolon to
1228
+ // prevent JavaScript from treating the previous line as a function call.
1229
+ func (c *GoToTSCompiler) needsDefensiveSemicolon(expr ast.Expr) bool {
1230
+ switch e := expr.(type) {
1231
+ case *ast.CallExpr:
1232
+ // Check if the function being called will be parenthesized
1233
+ // This happens when Fun is itself a CallExpr, TypeAssertExpr, or other complex expression
1234
+ switch e.Fun.(type) {
1235
+ case *ast.CallExpr:
1236
+ // (fn())() - needs defensive semicolon
1237
+ return true
1238
+ case *ast.TypeAssertExpr:
1239
+ // (x.(T))() - needs defensive semicolon
1240
+ return true
1241
+ case *ast.IndexExpr:
1242
+ // Could generate (arr[i])() if indexed result is called
1243
+ // But typically doesn't need defensive semicolon as arr[i]() is fine
1244
+ return false
1245
+ case *ast.ParenExpr:
1246
+ // Already parenthesized - needs defensive semicolon
1247
+ return true
1248
+ }
1249
+ case *ast.CompositeLit:
1250
+ // Array/slice literals start with '['
1251
+ if _, isArray := e.Type.(*ast.ArrayType); isArray {
1252
+ return true
1253
+ }
1254
+ case *ast.ParenExpr:
1255
+ // Parenthesized expressions start with '('
1256
+ return true
1257
+ }
1258
+ return false
1259
+ }