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
@@ -25,6 +25,9 @@ var builtinFunctions = map[string]bool{
25
25
  "recover": true,
26
26
  "print": true,
27
27
  "println": true,
28
+ "min": true,
29
+ "max": true,
30
+ "clear": true,
28
31
  }
29
32
 
30
33
  // writeBuiltinFunction handles built-in Go functions
@@ -34,7 +37,7 @@ func (c *GoToTSCompiler) writeBuiltinFunction(exp *ast.CallExpr, funName string)
34
37
  c.tsw.WriteLiterally("$.panic")
35
38
  return true, nil
36
39
  case "println":
37
- c.tsw.WriteLiterally("console.log")
40
+ c.tsw.WriteLiterally("$.println")
38
41
  return true, nil
39
42
  case "len":
40
43
  if len(exp.Args) != 1 {
@@ -89,6 +92,46 @@ func (c *GoToTSCompiler) writeBuiltinFunction(exp *ast.CallExpr, funName string)
89
92
  return true, nil
90
93
  case "append":
91
94
  return true, c.writeAppendCall(exp)
95
+ case "min":
96
+ if len(exp.Args) < 1 {
97
+ return true, errors.New("unhandled min call with no arguments")
98
+ }
99
+ c.tsw.WriteLiterally("Math.min(")
100
+ for i, arg := range exp.Args {
101
+ if i > 0 {
102
+ c.tsw.WriteLiterally(", ")
103
+ }
104
+ if err := c.WriteValueExpr(arg); err != nil {
105
+ return true, fmt.Errorf("failed to write argument %d in min call: %w", i, err)
106
+ }
107
+ }
108
+ c.tsw.WriteLiterally(")")
109
+ return true, nil
110
+ case "max":
111
+ if len(exp.Args) < 1 {
112
+ return true, errors.New("unhandled max call with no arguments")
113
+ }
114
+ c.tsw.WriteLiterally("Math.max(")
115
+ for i, arg := range exp.Args {
116
+ if i > 0 {
117
+ c.tsw.WriteLiterally(", ")
118
+ }
119
+ if err := c.WriteValueExpr(arg); err != nil {
120
+ return true, fmt.Errorf("failed to write argument %d in max call: %w", i, err)
121
+ }
122
+ }
123
+ c.tsw.WriteLiterally(")")
124
+ return true, nil
125
+ case "clear":
126
+ if len(exp.Args) != 1 {
127
+ return true, errors.Errorf("unhandled clear call with incorrect number of arguments: %d != 1", len(exp.Args))
128
+ }
129
+ c.tsw.WriteLiterally("$.clear(")
130
+ if err := c.WriteValueExpr(exp.Args[0]); err != nil {
131
+ return true, fmt.Errorf("failed to write argument in clear call: %w", err)
132
+ }
133
+ c.tsw.WriteLiterally(")")
134
+ return true, nil
92
135
  case "byte":
93
136
  if len(exp.Args) != 1 {
94
137
  return true, errors.Errorf("unhandled byte call with incorrect number of arguments: %d != 1", len(exp.Args))
@@ -119,13 +162,16 @@ func (c *GoToTSCompiler) writeAppendCall(exp *ast.CallExpr) error {
119
162
  }
120
163
 
121
164
  // The remaining arguments are the elements to append
122
- for i, arg := range exp.Args[1:] {
165
+ elemsToAppend := exp.Args[1:]
166
+ for i, arg := range elemsToAppend {
123
167
  if i > 0 || len(exp.Args) > 1 {
124
168
  c.tsw.WriteLiterally(", ")
125
169
  }
126
170
 
127
- // Special case: append([]byte, string...) should convert string to bytes
128
- if exp.Ellipsis != token.NoPos && i == 0 { // This is the first element after slice and has ellipsis
171
+ // Handle ellipsis (spread) for the last argument: append(slice, elems...)
172
+ // The ellipsis can only appear on the last argument, so check if this is the last element
173
+ isLastElement := i == len(elemsToAppend)-1
174
+ if exp.Ellipsis != token.NoPos && isLastElement {
129
175
  // Check if the slice is []byte and the argument is a string
130
176
  sliceType := c.pkg.TypesInfo.TypeOf(exp.Args[0])
131
177
  argType := c.pkg.TypesInfo.TypeOf(arg)
@@ -141,6 +187,16 @@ func (c *GoToTSCompiler) writeAppendCall(exp *ast.CallExpr) error {
141
187
  continue
142
188
  }
143
189
  }
190
+
191
+ // For other slice types with ellipsis, use spread operator
192
+ // append(slice, anotherSlice...) -> $.append(slice, ...(anotherSlice || []))
193
+ // The || [] handles the case where anotherSlice is null (nil in Go)
194
+ c.tsw.WriteLiterally("...(")
195
+ if err := c.WriteValueExpr(arg); err != nil {
196
+ return fmt.Errorf("failed to write spread argument in append call: %w", err)
197
+ }
198
+ c.tsw.WriteLiterally(" || [])")
199
+ continue
144
200
  }
145
201
 
146
202
  if err := c.WriteValueExpr(arg); err != nil {
@@ -18,14 +18,33 @@ func (c *GoToTSCompiler) writeNilConversion(exp *ast.CallExpr) (handled bool, er
18
18
  return false, nil
19
19
  }
20
20
 
21
- // Handle nil pointer to struct type conversions: (*struct{})(nil)
22
- if starExpr, isStarExpr := exp.Fun.(*ast.StarExpr); isStarExpr {
23
- if _, isStructType := starExpr.X.(*ast.StructType); isStructType {
24
- c.tsw.WriteLiterally("null")
21
+ // Check if this is actually a type conversion, not a method/function call
22
+ // For type conversions, exp.Fun is a type expression (IsType() is true)
23
+ // For method calls like s.ptr.Swap(nil), exp.Fun is a selector expression (IsType() is false)
24
+ if tv, ok := c.pkg.TypesInfo.Types[exp.Fun]; !ok || !tv.IsType() {
25
+ // This is not a type conversion, let the normal call handling proceed
26
+ return false, nil
27
+ }
28
+
29
+ // Get the type being converted to
30
+ if typ := c.pkg.TypesInfo.TypeOf(exp.Fun); typ != nil {
31
+ // For pointer types, create a typed nil that preserves type information
32
+ if ptrType, ok := typ.(*types.Pointer); ok {
33
+ // Use a qualifier that returns the package name for local types
34
+ // This matches Go's reflect output format (e.g., "main.Stringer")
35
+ qualifier := func(pkg *types.Package) string {
36
+ if pkg == nil {
37
+ return ""
38
+ }
39
+ return pkg.Name()
40
+ }
41
+ typeName := types.TypeString(ptrType, qualifier)
42
+ c.tsw.WriteLiterallyf("$.typedNil(%q)", typeName)
25
43
  return true, nil
26
44
  }
27
45
  }
28
46
 
47
+ // For non-pointer types (or if type info is unavailable), use plain null
29
48
  c.tsw.WriteLiterally("null")
30
49
  return true, nil
31
50
  }
@@ -141,8 +160,21 @@ func (c *GoToTSCompiler) writeStringConversion(exp *ast.CallExpr) error {
141
160
 
142
161
  // Handle direct string(int32) conversion
143
162
  if tv, ok := c.pkg.TypesInfo.Types[arg]; ok {
144
- // Case 3a: Argument is already a string - no-op
163
+ // Case 3a: Argument is already a string - no-op (unless it's a named type with toString)
145
164
  if c.isStringType(tv.Type) {
165
+ // Check if this is a named type from the reflect package (like StructTag)
166
+ // which is implemented as a class in TypeScript with a toString() method
167
+ if namedType, isNamed := tv.Type.(*types.Named); isNamed {
168
+ obj := namedType.Obj()
169
+ if obj != nil && obj.Pkg() != nil && obj.Pkg().Path() == "reflect" && obj.Name() == "StructTag" {
170
+ // Call toString() for reflect.StructTag
171
+ if err := c.WriteValueExpr(arg); err != nil {
172
+ return fmt.Errorf("failed to write argument for string(reflect.StructTag) conversion: %w", err)
173
+ }
174
+ c.tsw.WriteLiterally(".toString()")
175
+ return nil
176
+ }
177
+ }
146
178
  // Translate string(stringValue) to stringValue (no-op)
147
179
  if err := c.WriteValueExpr(arg); err != nil {
148
180
  return fmt.Errorf("failed to write argument for string(string) no-op conversion: %w", err)
@@ -61,7 +61,8 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
61
61
  // For built-ins that don't return early, write the arguments
62
62
  if funIdent.String() != "new" && funIdent.String() != "close" && funIdent.String() != "make" &&
63
63
  funIdent.String() != "string" && funIdent.String() != "append" && funIdent.String() != "byte" &&
64
- funIdent.String() != "int" {
64
+ funIdent.String() != "int" && funIdent.String() != "min" && funIdent.String() != "max" &&
65
+ funIdent.String() != "clear" {
65
66
  return c.writeCallArguments(exp)
66
67
  }
67
68
  return nil
@@ -117,14 +118,26 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
117
118
  }
118
119
  c.tsw.WriteLiterally(")")
119
120
  } else {
121
+ // Check if this is a function call that returns a function (e.g., simpleIterator(m)())
122
+ // and if the inner call is async, wrap it in parentheses
123
+ innerCallExpr, isCallExpr := expFun.(*ast.CallExpr)
124
+ needsParens := isCallExpr && c.isCallExprAsync(innerCallExpr)
125
+
126
+ if needsParens {
127
+ c.tsw.WriteLiterally("(")
128
+ }
129
+
120
130
  // Not an identifier (e.g., method call on a value, function call result)
121
131
  if err := c.WriteValueExpr(expFun); err != nil {
122
132
  return fmt.Errorf("failed to write method expression in call: %w", err)
123
133
  }
124
134
 
125
- // Check if this is a function call that returns a function (e.g., simpleIterator(m)())
135
+ if needsParens {
136
+ c.tsw.WriteLiterally(")")
137
+ }
138
+
126
139
  // Add non-null assertion since the returned function could be null
127
- if _, isCallExpr := expFun.(*ast.CallExpr); isCallExpr {
140
+ if isCallExpr {
128
141
  c.tsw.WriteLiterally("!")
129
142
  } else {
130
143
  c.addNonNullAssertion(expFun)
@@ -210,8 +223,8 @@ func (c *GoToTSCompiler) writeArgumentWithTypeHandling(arg ast.Expr, funcSig *ty
210
223
  return nil
211
224
  }
212
225
 
213
- // resolveImportAlias returns the import alias for a given package
214
- // This is the single source of truth for import alias resolution
226
+ // resolveImportAlias returns the import alias for a given package.
227
+ // This is the single source of truth for import alias resolution.
215
228
  func (c *GoToTSCompiler) resolveImportAlias(pkg *types.Package) (alias string, found bool) {
216
229
  if c.analysis == nil || pkg == nil {
217
230
  return "", false
@@ -219,11 +232,18 @@ func (c *GoToTSCompiler) resolveImportAlias(pkg *types.Package) (alias string, f
219
232
 
220
233
  pkgName := pkg.Name()
221
234
 
222
- // Try to match by the actual package name
235
+ // Try to match by the actual package name in regular imports
223
236
  if _, exists := c.analysis.Imports[pkgName]; exists {
224
237
  return pkgName, true
225
238
  }
226
239
 
240
+ // Also check synthetic imports for the current file
241
+ if syntheticImports := c.analysis.SyntheticImportsPerFile[c.currentFilePath]; syntheticImports != nil {
242
+ if _, exists := syntheticImports[pkgName]; exists {
243
+ return pkgName, true
244
+ }
245
+ }
246
+
227
247
  return "", false
228
248
  }
229
249
 
@@ -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,66 @@ 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
+ // Check if this is integer division (Go's / operator truncates for integers)
476
+ isIntegerDivision := false
477
+ if exp.Op == token.QUO && c.pkg != nil && c.pkg.TypesInfo != nil {
478
+ leftType := c.pkg.TypesInfo.TypeOf(exp.X)
479
+ rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
480
+ if leftType != nil && rightType != nil {
481
+ leftBasic, leftIsBasic := leftType.Underlying().(*types.Basic)
482
+ rightBasic, rightIsBasic := rightType.Underlying().(*types.Basic)
483
+ if leftIsBasic && rightIsBasic {
484
+ // Check if both are integer types (not floating point)
485
+ isIntegerDivision = (leftBasic.Info()&types.IsInteger != 0) && (rightBasic.Info()&types.IsInteger != 0)
486
+ }
487
+ }
488
+ }
489
+
490
+ if isIntegerDivision {
491
+ // Wrap integer division in Math.trunc() to match Go's integer division behavior
492
+ c.tsw.WriteLiterally("Math.trunc(")
493
+ }
494
+
495
+ if needsNumberCast {
496
+ c.tsw.WriteLiterally("Number(")
497
+ }
444
498
  if err := c.WriteValueExpr(exp.X); err != nil {
445
499
  return fmt.Errorf("failed to write binary expression left operand: %w", err)
446
500
  }
501
+ if needsNumberCast {
502
+ c.tsw.WriteLiterally(")")
503
+ }
447
504
 
448
505
  c.tsw.WriteLiterally(" ")
449
506
  tokStr, ok := TokenToTs(exp.Op)
@@ -458,6 +515,10 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
458
515
  return fmt.Errorf("failed to write binary expression right operand: %w", err)
459
516
  }
460
517
 
518
+ if isIntegerDivision {
519
+ c.tsw.WriteLiterally(")") // Close Math.trunc()
520
+ }
521
+
461
522
  if isBitwise {
462
523
  c.tsw.WriteLiterally(")") // Add closing parenthesis for bitwise operations
463
524
  }
@@ -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,15 @@ 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 syntheticImports := c.analysis.SyntheticImportsPerFile[c.currentFilePath]; syntheticImports != nil {
642
+ if _, isSynthetic := syntheticImports[impName]; isSynthetic {
643
+ return
644
+ }
645
+ }
646
+
628
647
  c.tsw.WriteImport(impName, tsImportPath+"/index.js")
629
648
  }
630
649