goscript 0.0.61 → 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 (81) hide show
  1. package/README.md +9 -0
  2. package/compiler/analysis.go +536 -15
  3. package/compiler/assignment.go +72 -0
  4. package/compiler/compiler.go +64 -11
  5. package/compiler/composite-lit.go +29 -8
  6. package/compiler/decl.go +20 -11
  7. package/compiler/expr-call-async.go +26 -1
  8. package/compiler/expr-call-builtins.go +60 -4
  9. package/compiler/expr-call-type-conversion.go +37 -5
  10. package/compiler/expr-call.go +16 -3
  11. package/compiler/expr-selector.go +35 -2
  12. package/compiler/expr-type.go +12 -2
  13. package/compiler/expr.go +37 -0
  14. package/compiler/index.test.ts +3 -1
  15. package/compiler/lit.go +13 -4
  16. package/compiler/spec-struct.go +30 -8
  17. package/compiler/spec-value.go +2 -2
  18. package/compiler/spec.go +21 -4
  19. package/compiler/stmt-assign.go +71 -0
  20. package/compiler/stmt-range.go +2 -2
  21. package/compiler/stmt.go +128 -0
  22. package/compiler/type-utils.go +40 -1
  23. package/compiler/type.go +50 -12
  24. package/dist/gs/builtin/builtin.d.ts +8 -1
  25. package/dist/gs/builtin/builtin.js +26 -1
  26. package/dist/gs/builtin/builtin.js.map +1 -1
  27. package/dist/gs/builtin/errors.d.ts +1 -0
  28. package/dist/gs/builtin/errors.js +8 -0
  29. package/dist/gs/builtin/errors.js.map +1 -1
  30. package/dist/gs/builtin/slice.d.ts +5 -4
  31. package/dist/gs/builtin/slice.js +45 -14
  32. package/dist/gs/builtin/slice.js.map +1 -1
  33. package/dist/gs/builtin/type.d.ts +23 -2
  34. package/dist/gs/builtin/type.js +125 -0
  35. package/dist/gs/builtin/type.js.map +1 -1
  36. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  37. package/dist/gs/bytes/reader.gs.js +1 -1
  38. package/dist/gs/bytes/reader.gs.js.map +1 -1
  39. package/dist/gs/reflect/index.d.ts +2 -2
  40. package/dist/gs/reflect/index.js +1 -1
  41. package/dist/gs/reflect/index.js.map +1 -1
  42. package/dist/gs/reflect/map.d.ts +3 -2
  43. package/dist/gs/reflect/map.js +37 -3
  44. package/dist/gs/reflect/map.js.map +1 -1
  45. package/dist/gs/reflect/type.d.ts +50 -12
  46. package/dist/gs/reflect/type.js +820 -27
  47. package/dist/gs/reflect/type.js.map +1 -1
  48. package/dist/gs/reflect/types.d.ts +11 -12
  49. package/dist/gs/reflect/types.js +26 -15
  50. package/dist/gs/reflect/types.js.map +1 -1
  51. package/dist/gs/reflect/value.d.ts +4 -4
  52. package/dist/gs/reflect/value.js +8 -2
  53. package/dist/gs/reflect/value.js.map +1 -1
  54. package/dist/gs/slices/slices.d.ts +21 -0
  55. package/dist/gs/slices/slices.js +48 -0
  56. package/dist/gs/slices/slices.js.map +1 -1
  57. package/dist/gs/sync/atomic/type.gs.d.ts +2 -2
  58. package/dist/gs/sync/atomic/type.gs.js +12 -2
  59. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  60. package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
  61. package/dist/gs/unicode/utf8/utf8.js +10 -6
  62. package/dist/gs/unicode/utf8/utf8.js.map +1 -1
  63. package/go.mod +4 -4
  64. package/go.sum +8 -8
  65. package/gs/builtin/builtin.ts +27 -2
  66. package/gs/builtin/errors.ts +12 -0
  67. package/gs/builtin/slice.ts +71 -7
  68. package/gs/builtin/type.ts +159 -2
  69. package/gs/bytes/reader.gs.ts +2 -2
  70. package/gs/math/hypot.gs.test.ts +3 -1
  71. package/gs/math/pow10.gs.test.ts +5 -4
  72. package/gs/reflect/index.ts +3 -2
  73. package/gs/reflect/map.test.ts +7 -6
  74. package/gs/reflect/map.ts +49 -7
  75. package/gs/reflect/type.ts +1053 -54
  76. package/gs/reflect/types.ts +34 -21
  77. package/gs/reflect/value.ts +12 -6
  78. package/gs/slices/slices.ts +55 -0
  79. package/gs/sync/atomic/type.gs.ts +14 -5
  80. package/gs/unicode/utf8/utf8.ts +12 -8
  81. package/package.json +13 -13
@@ -130,10 +130,23 @@ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
130
130
 
131
131
  // Fallback / Normal Case (e.g., obj.Field, pkg.Var, method calls)
132
132
  // WriteValueExpr handles adding .value for the base variable itself if it's varrefed.
133
+
134
+ // Check if the base expression is an async call - if so, we need to wrap it in parentheses
135
+ // so that await binds correctly: (await asyncFn()).method instead of await asyncFn().method
136
+ needsParensForAsync := false
137
+ if callExpr, isCall := exp.X.(*ast.CallExpr); isCall && c.isCallExprAsync(callExpr) {
138
+ needsParensForAsync = true
139
+ c.tsw.WriteLiterally("(")
140
+ }
141
+
133
142
  if err := c.WriteValueExpr(exp.X); err != nil {
134
143
  return fmt.Errorf("failed to write selector base expression: %w", err)
135
144
  }
136
145
 
146
+ if needsParensForAsync {
147
+ c.tsw.WriteLiterally(")")
148
+ }
149
+
137
150
  // Add null assertion for selector expressions when accessing fields/methods on nullable types
138
151
  // In Go, accessing fields or calling methods on nil pointers/interfaces panics, so we should throw in TypeScript
139
152
  baseType := c.pkg.TypesInfo.TypeOf(exp.X)
@@ -212,8 +225,24 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
212
225
  return fmt.Errorf("failed to get qualified type name for primitive receiver")
213
226
  }
214
227
 
215
- // Generate: () => TypeName_MethodName(receiverValue)
216
- c.tsw.WriteLiterally("(() => ")
228
+ // Get the method parameters to forward them
229
+ params := sig.Params()
230
+ paramNames := make([]string, params.Len())
231
+ for i := 0; i < params.Len(); i++ {
232
+ paramNames[i] = fmt.Sprintf("_p%d", i)
233
+ }
234
+
235
+ // Generate: ((_p0: type0, _p1: type1, ...) => TypeName_MethodName(receiverValue, _p0, _p1, ...))
236
+ c.tsw.WriteLiterally("((")
237
+ for i, name := range paramNames {
238
+ if i > 0 {
239
+ c.tsw.WriteLiterally(", ")
240
+ }
241
+ c.tsw.WriteLiterally(name)
242
+ c.tsw.WriteLiterally(": ")
243
+ c.WriteGoType(params.At(i).Type(), GoTypeContextGeneral)
244
+ }
245
+ c.tsw.WriteLiterally(") => ")
217
246
  c.tsw.WriteLiterally(typeName)
218
247
  c.tsw.WriteLiterally("_")
219
248
  c.tsw.WriteLiterally(exp.Sel.Name)
@@ -221,6 +250,10 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
221
250
  if err := c.WriteValueExpr(exp.X); err != nil {
222
251
  return fmt.Errorf("failed to write method value receiver: %w", err)
223
252
  }
253
+ for _, name := range paramNames {
254
+ c.tsw.WriteLiterally(", ")
255
+ c.tsw.WriteLiterally(name)
256
+ }
224
257
  c.tsw.WriteLiterally("))")
225
258
 
226
259
  return nil
@@ -26,13 +26,23 @@ func (c *GoToTSCompiler) WriteTypeExpr(a ast.Expr) {
26
26
  // Check if this is a package selector (e.g., os.FileInfo)
27
27
  if obj := c.pkg.TypesInfo.Uses[pkgIdent]; obj != nil {
28
28
  if _, isPkg := obj.(*types.PkgName); isPkg {
29
- // This is a package.Type reference - write the qualified name
29
+ // This is a package.Type reference
30
+ typ := c.pkg.TypesInfo.TypeOf(a)
31
+
32
+ // Check if this is an interface type and add null | prefix
33
+ if typ != nil {
34
+ if _, isInterface := typ.Underlying().(*types.Interface); isInterface {
35
+ c.tsw.WriteLiterally("null | ")
36
+ }
37
+ }
38
+
39
+ // Write the qualified name
30
40
  c.tsw.WriteLiterally(pkgIdent.Name)
31
41
  c.tsw.WriteLiterally(".")
32
42
  c.tsw.WriteLiterally(selectorExpr.Sel.Name)
33
43
 
34
44
  // Check if this is a function type and add | null
35
- if typ := c.pkg.TypesInfo.TypeOf(a); typ != nil {
45
+ if typ != nil {
36
46
  if namedType, isNamed := typ.(*types.Named); isNamed {
37
47
  if _, isSignature := namedType.Underlying().(*types.Signature); isSignature {
38
48
  c.tsw.WriteLiterally(" | null")
package/compiler/expr.go CHANGED
@@ -441,9 +441,46 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
441
441
  c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
442
442
  }
443
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
+ }
444
478
  if err := c.WriteValueExpr(exp.X); err != nil {
445
479
  return fmt.Errorf("failed to write binary expression left operand: %w", err)
446
480
  }
481
+ if needsNumberCast {
482
+ c.tsw.WriteLiterally(")")
483
+ }
447
484
 
448
485
  c.tsw.WriteLiterally(" ")
449
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)
@@ -479,13 +490,23 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
479
490
  if fieldKeyName == "_" {
480
491
  continue
481
492
  }
482
- // fieldTsType := c.getTypeString(field.Type())
483
493
  if !firstField {
484
494
  c.tsw.WriteLiterally(", ")
485
495
  }
486
496
  firstField = false
487
497
  c.tsw.WriteLiterallyf("%q: ", fieldKeyName)
488
- 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
+ }
489
510
  }
490
511
  c.tsw.WriteLiterally("}")
491
512
  c.tsw.WriteLine("")
@@ -543,15 +564,16 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
543
564
  fieldType = ptr.Elem()
544
565
  }
545
566
 
546
- if named, ok := fieldType.(*types.Named); ok {
547
- embeddedName := named.Obj().Name()
567
+ if _, ok := fieldType.(*types.Named); ok {
548
568
  // Check if the embedded type is an interface
549
569
  if _, isInterface := fieldType.Underlying().(*types.Interface); isInterface {
550
570
  // For embedded interfaces, use the full qualified interface type
551
571
  embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = c.getTypeString(field.Type())
552
572
  } else {
553
573
  // For embedded structs, use ConstructorParameters for field-based initialization
554
- 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)
555
577
  }
556
578
  }
557
579
  continue
@@ -573,7 +595,7 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
573
595
  for name := range fieldMap {
574
596
  fieldNames = append(fieldNames, name)
575
597
  }
576
- sort.Strings(fieldNames)
598
+ slices.Sort(fieldNames)
577
599
 
578
600
  var fieldDefs []string
579
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)
@@ -406,11 +407,20 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
406
407
  c.WriteTypeExpr(funcDecl.Type.Results.List[0].Type)
407
408
  } else {
408
409
  c.tsw.WriteLiterally("[")
409
- for i, field := range funcDecl.Type.Results.List {
410
- if i > 0 {
411
- 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)
412
423
  }
413
- c.WriteTypeExpr(field.Type)
414
424
  }
415
425
  c.tsw.WriteLiterally("]")
416
426
  }
@@ -625,6 +635,13 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
625
635
  importVars: make(map[string]struct{}),
626
636
  }
627
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
+
628
645
  c.tsw.WriteImport(impName, tsImportPath+"/index.js")
629
646
  }
630
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
@@ -574,6 +583,12 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
574
583
  }
575
584
  }
576
585
  }
586
+
587
+ // Special handling for primitive types that implement error interface
588
+ if c.writePrimitiveErrorWrapperIfNeeded(exp, res, i) {
589
+ continue
590
+ }
591
+
577
592
  if err := c.WriteValueExpr(res); err != nil { // Return results are values
578
593
  return err
579
594
  }
@@ -586,6 +601,84 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
586
601
  return nil
587
602
  }
588
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
+
589
682
  // WriteStmtBlock translates a Go block statement (`ast.BlockStmt`), typically
590
683
  // `{ ...stmts... }`, into its TypeScript equivalent, carefully preserving
591
684
  // comments and blank lines to maintain code readability and structure.
@@ -1129,3 +1222,38 @@ func (c *GoToTSCompiler) substituteExprForShadowing(expr ast.Expr, shadowingInfo
1129
1222
  func (c *GoToTSCompiler) isBuiltinFunction(name string) bool {
1130
1223
  return builtinFunctions[name]
1131
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
+ }
@@ -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