goscript 0.0.23 → 0.0.24

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 (53) hide show
  1. package/README.md +1 -1
  2. package/cmd/goscript/cmd_compile.go +1 -1
  3. package/compiler/analysis.go +73 -131
  4. package/compiler/analysis_test.go +220 -0
  5. package/compiler/assignment.go +37 -43
  6. package/compiler/builtin_test.go +102 -0
  7. package/compiler/compiler.go +79 -14
  8. package/compiler/composite-lit.go +108 -43
  9. package/compiler/config.go +7 -3
  10. package/compiler/config_test.go +6 -33
  11. package/compiler/expr-selector.go +66 -41
  12. package/compiler/expr-star.go +57 -65
  13. package/compiler/expr-type.go +1 -1
  14. package/compiler/expr-value.go +1 -1
  15. package/compiler/expr.go +79 -18
  16. package/compiler/primitive.go +11 -10
  17. package/compiler/spec-struct.go +3 -3
  18. package/compiler/spec-value.go +75 -29
  19. package/compiler/spec.go +9 -3
  20. package/compiler/stmt-assign.go +36 -2
  21. package/compiler/stmt-for.go +11 -0
  22. package/compiler/stmt-range.go +110 -0
  23. package/compiler/stmt.go +52 -0
  24. package/compiler/type.go +36 -11
  25. package/dist/gs/builtin/builtin.js +37 -0
  26. package/dist/gs/builtin/builtin.js.map +1 -0
  27. package/dist/gs/builtin/channel.js +471 -0
  28. package/dist/gs/builtin/channel.js.map +1 -0
  29. package/dist/gs/builtin/defer.js +54 -0
  30. package/dist/gs/builtin/defer.js.map +1 -0
  31. package/dist/gs/builtin/io.js +15 -0
  32. package/dist/gs/builtin/io.js.map +1 -0
  33. package/dist/gs/builtin/map.js +44 -0
  34. package/dist/gs/builtin/map.js.map +1 -0
  35. package/dist/gs/builtin/slice.js +799 -0
  36. package/dist/gs/builtin/slice.js.map +1 -0
  37. package/dist/gs/builtin/type.js +745 -0
  38. package/dist/gs/builtin/type.js.map +1 -0
  39. package/dist/gs/builtin/varRef.js +14 -0
  40. package/dist/gs/builtin/varRef.js.map +1 -0
  41. package/dist/gs/context/context.js +55 -0
  42. package/dist/gs/context/context.js.map +1 -0
  43. package/dist/gs/context/index.js +2 -0
  44. package/dist/gs/context/index.js.map +1 -0
  45. package/dist/gs/runtime/index.js +2 -0
  46. package/dist/gs/runtime/index.js.map +1 -0
  47. package/dist/gs/runtime/runtime.js +158 -0
  48. package/dist/gs/runtime/runtime.js.map +1 -0
  49. package/dist/gs/time/index.js +2 -0
  50. package/dist/gs/time/index.js.map +1 -0
  51. package/dist/gs/time/time.js +115 -0
  52. package/dist/gs/time/time.js.map +1 -0
  53. package/package.json +3 -2
@@ -1,89 +1,81 @@
1
1
  package compiler
2
2
 
3
3
  import (
4
- "fmt"
5
4
  "go/ast"
5
+ "go/types"
6
6
  )
7
7
 
8
8
  // WriteStarExpr translates a Go pointer dereference expression (`ast.StarExpr`, e.g., `*p`)
9
9
  // into its TypeScript equivalent. This involves careful handling of Go's pointers
10
- // and TypeScript's boxing mechanism for emulating pointer semantics.
10
+ // and TypeScript's varRefing mechanism for emulating pointer semantics.
11
11
  //
12
- // The translation depends on whether the pointer variable `p` itself is boxed and
12
+ // The translation depends on whether the pointer variable `p` itself is varrefed and
13
13
  // what type of value it points to:
14
- // 1. If `p` is not boxed and points to a primitive or another pointer: `*p` -> `p!.value`.
15
- // (`p` holds a box, so dereference accesses its `value` field).
16
- // 2. If `p` is not boxed and points to a struct: `*p` -> `p!`.
14
+ // 1. If `p` is not varrefed and points to a primitive or another pointer: `*p` -> `p!.value`.
15
+ // (`p` holds a varRef, so dereference accesses its `value` field).
16
+ // 2. If `p` is not varrefed and points to a struct: `*p` -> `p!`.
17
17
  // (`p` holds the struct instance directly; structs are reference types in TS).
18
- // 3. If `p` is boxed (i.e., `p` is `$.Box<PointerType>`) and points to a primitive/pointer:
19
- // `*p` -> `p.value!.value`.
20
- // (First `.value` unboxes `p`, then `!.value` dereferences the inner pointer).
21
- // 4. If `p` is boxed and points to a struct: `*p` -> `p.value!`.
22
- // (First `.value` unboxes `p` to get the struct instance).
18
+ // 3. If `p` is variable referenced (i.e., `p` is `$.VarRef<PointerType>`) and points to a primitive/pointer:
19
+ // `p.value!.value` (access the variable reference, then dereference the pointer)
20
+ // 4. If `p` is varrefed and points to a struct: `p.value!`.
21
+ // (First `.value` unvarRefes `p` to get the struct instance).
23
22
  //
24
- // `WriteValueExpr(operand)` handles the initial unboxing of `p` if `p` itself is a boxed variable.
23
+ // `WriteValueExpr(operand)` handles the initial unvarRefing of `p` if `p` itself is a varrefed variable.
25
24
  // A non-null assertion `!` is always added as pointers can be nil.
26
- // `c.analysis.NeedsBoxedDeref(ptrType)` determines if an additional `.value` is needed
27
- // based on whether the dereferenced type is a primitive/pointer (requires `.value`) or
28
- // a struct (does not require `.value`).
25
+ // The function determines if `.value` access is needed by checking what the Go pointer operand points to.
26
+ //
27
+ // For multi-level dereferences like `***p`, this function is called recursively, with each level
28
+ // adding the appropriate `!.value` suffix.
29
+ //
30
+ // Examples:
31
+ // - Simple pointer to primitive: `p!.value` (where p is *int)
32
+ // - Variable referenced pointer to primitive: `p.value!.value` (where p is VarRef<*int>)
33
+ // Example: let p = $.varRef(x) (where x is another variable reference) => p.value!.value
34
+ // - Pointer to struct: `p!` (where p is *MyStruct)
35
+ // Example: let p = $.varRef(new MyStruct()) => p.value!
36
+ // - Variable referenced pointer to struct: `p.value!` (where p is VarRef<*MyStruct>)
37
+ // - Triple pointer: `p3!.value!.value!.value` (where p3 is VarRef<VarRef<VarRef<number> | null> | null> | null)
29
38
  func (c *GoToTSCompiler) WriteStarExpr(exp *ast.StarExpr) error {
30
- // Generate code for a pointer dereference expression (*p).
31
- //
32
- // IMPORTANT: Pointer dereferencing in TypeScript requires careful handling of the box/unbox state:
33
- //
34
- // 1. p!.value - when p is not boxed and points to a primitive/pointer
35
- // Example: let p = x (where x is a box) => p!.value
36
- //
37
- // 2. p! - when p is not boxed and points to a struct
38
- // Example: let p = new MyStruct() => p! (structs are reference types)
39
- //
40
- // 3. p.value!.value - when p is boxed and points to a primitive/pointer
41
- // Example: let p = $.box(x) (where x is another box) => p.value!.value
42
- //
43
- // 4. p.value! - when p is boxed and points to a struct
44
- // Example: let p = $.box(new MyStruct()) => p.value!
45
- //
46
- // Critical bug fix: We must handle each case correctly to avoid over-dereferencing
47
- // (adding too many .value) or under-dereferencing (missing .value where needed)
48
- //
49
- // NOTE: This logic aligns with design/BOXES_POINTERS.md.
50
-
51
- // Get the operand expression and its type information
52
- operand := exp.X
53
-
54
- // Get the type of the operand (the pointer being dereferenced)
55
- ptrType := c.pkg.TypesInfo.TypeOf(operand)
56
-
57
- // Special case for handling multi-level dereferencing:
58
- // Check if the operand is itself a StarExpr (e.g., **p or ***p)
59
- // We need to handle these specially to correctly generate nested .value accesses
60
- if starExpr, isStarExpr := operand.(*ast.StarExpr); isStarExpr {
61
- // First, write the inner star expression
62
- if err := c.WriteStarExpr(starExpr); err != nil {
63
- return fmt.Errorf("failed to write inner star expression: %w", err)
39
+ // Check if the operand is an identifier that is varrefed
40
+ isVarrefedIdent := false
41
+ if ident, ok := exp.X.(*ast.Ident); ok {
42
+ if obj := c.pkg.TypesInfo.ObjectOf(ident); obj != nil {
43
+ isVarrefedIdent = c.analysis.NeedsVarRef(obj)
64
44
  }
65
-
66
- // Always add .value for multi-level dereferences
67
- // For expressions like **p, each * adds a .value
68
- c.tsw.WriteLiterally("!.value")
69
- return nil
70
45
  }
71
46
 
72
- // Standard case: single-level dereference
73
- // Write the pointer expression, which will access .value if the variable is boxed
74
- // WriteValueExpr will add .value if the variable itself is boxed (p.value)
75
- if err := c.WriteValueExpr(operand); err != nil {
76
- return fmt.Errorf("failed to write star expression operand: %w", err)
47
+ // Write the operand
48
+ if isVarrefedIdent {
49
+ // For varrefed identifiers, we need to access the value first
50
+ if err := c.WriteValueExpr(exp.X); err != nil {
51
+ return err
52
+ }
53
+ } else {
54
+ // For non-varrefed identifiers and other expressions
55
+ switch operand := exp.X.(type) {
56
+ case *ast.Ident:
57
+ // Write identifier without .value access
58
+ c.WriteIdent(operand, false)
59
+ default:
60
+ // For other expressions (like nested star expressions), use WriteValueExpr
61
+ if err := c.WriteValueExpr(exp.X); err != nil {
62
+ return err
63
+ }
64
+ }
77
65
  }
78
66
 
79
- // Add ! for null assertion - all pointers can be null in TypeScript
67
+ // Add non-null assertion for pointer safety
80
68
  c.tsw.WriteLiterally("!")
81
69
 
82
- // Add .value only if we need boxed dereferencing for this type of pointer
83
- // This depends on whether we're dereferencing to a primitive (needs .value)
84
- // or to a struct (no .value needed)
85
- if c.analysis.NeedsBoxedDeref(ptrType) {
86
- c.tsw.WriteLiterally(".value")
70
+ // Check what the operand points to (not what the result is)
71
+ operandType := c.pkg.TypesInfo.TypeOf(exp.X)
72
+ if ptrType, isPtr := operandType.(*types.Pointer); isPtr {
73
+ elemType := ptrType.Elem()
74
+ // Only add .value if NOT pointing to a struct
75
+ if _, isStruct := elemType.Underlying().(*types.Struct); !isStruct {
76
+ c.tsw.WriteLiterally(".value")
77
+ }
78
+ // If pointing to a struct, don't add .value (structs are reference types in TS)
87
79
  }
88
80
 
89
81
  return nil
@@ -11,7 +11,7 @@ import (
11
11
  // It handles various Go type expressions:
12
12
  // - Basic types (e.g., int, string, bool) -> TypeScript primitives (number, string, boolean)
13
13
  // - Named types -> TypeScript class/interface names
14
- // - Pointer types (`*T`) -> `$.Box<T_ts> | null`
14
+ // - Pointer types (`*T`) -> `$.VarRef<T_ts> | null`
15
15
  // - Slice types (`[]T`) -> `$.Slice<T_ts>`
16
16
  // - Array types (`[N]T`) -> `T_ts[]`
17
17
  // - Map types (`map[K]V`) -> `Map<K_ts, V_ts>`
@@ -7,7 +7,7 @@ import (
7
7
  // WriteValueExpr translates a Go abstract syntax tree (AST) expression (`ast.Expr`)
8
8
  // that represents a value into its TypeScript value equivalent.
9
9
  // This is a central dispatch function for various expression types:
10
- // - Identifiers (`ast.Ident`): Delegates to `WriteIdent`, potentially adding `.value` for boxed variables.
10
+ // - Identifiers (`ast.Ident`): Delegates to `WriteIdent`, potentially adding `.value` for varrefed variables.
11
11
  // - Selector expressions (`ast.SelectorExpr`, e.g., `obj.Field` or `pkg.Var`): Delegates to `WriteSelectorExpr`.
12
12
  // - Pointer dereferences (`ast.StarExpr`, e.g., `*ptr`): Delegates to `WriteStarExpr`.
13
13
  // - Function calls (`ast.CallExpr`): Delegates to `WriteCallExpr`.
package/compiler/expr.go CHANGED
@@ -157,7 +157,7 @@ func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
157
157
  // of the left (X) and right (Y) operands of the binary expression.
158
158
  // Returns `true` if both operands are determined to be pointer types,
159
159
  // `false` otherwise. This is used to apply specific comparison semantics
160
- // for pointers (e.g., comparing the box objects directly).
160
+ // for pointers (e.g., comparing the varRef objects directly).
161
161
  func (c *GoToTSCompiler) isPointerComparison(exp *ast.BinaryExpr) bool {
162
162
  leftType := c.pkg.TypesInfo.TypeOf(exp.X)
163
163
  rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
@@ -195,10 +195,10 @@ func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
195
195
  // It handles several cases:
196
196
  // - Channel send (`ch <- val`): Becomes `await ch.send(val)`.
197
197
  // - Nil comparison for pointers (`ptr == nil` or `ptr != nil`): Compares the
198
- // pointer (which may be a box object or `null`) directly to `null` using
198
+ // pointer (which may be a varRef object or `null`) directly to `null` using
199
199
  // the translated operator (`==` or `!=`).
200
200
  // - Pointer comparison (non-nil, `ptr1 == ptr2` or `ptr1 != ptr2`): Compares
201
- // the box objects directly using strict equality (`===` or `!==`).
201
+ // the varRef objects directly using strict equality (`===` or `!==`).
202
202
  // - Bitwise operations (`&`, `|`, `^`, `<<`, `>>`, `&^`): The expression is wrapped
203
203
  // in parentheses `()` to ensure correct precedence in TypeScript, and operators
204
204
  // are mapped (e.g., `&^` might need special handling or is mapped to a runtime helper).
@@ -243,9 +243,26 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
243
243
  }
244
244
 
245
245
  if isNilComparison {
246
- // Compare the box object directly to null
247
- if err := c.WriteValueExpr(ptrExpr); err != nil {
248
- return fmt.Errorf("failed to write pointer expression in nil comparison: %w", err)
246
+ // For nil comparisons, we need to decide whether to write .value or not
247
+ // If the pointer variable is varrefed, we need to access .value
248
+ if ident, ok := ptrExpr.(*ast.Ident); ok {
249
+ if obj := c.pkg.TypesInfo.ObjectOf(ident); obj != nil {
250
+ if c.analysis.NeedsVarRef(obj) {
251
+ // Variable is varrefed, so we need to access .value
252
+ c.WriteIdent(ident, true) // This will add .value
253
+ } else {
254
+ // Variable is not varrefed, write directly
255
+ c.WriteIdent(ident, false)
256
+ }
257
+ } else {
258
+ // No object info, write directly
259
+ c.WriteIdent(ident, false)
260
+ }
261
+ } else {
262
+ // For other expressions, use WriteValueExpr (but this might need review)
263
+ if err := c.WriteValueExpr(ptrExpr); err != nil {
264
+ return fmt.Errorf("failed to write pointer expression in nil comparison: %w", err)
265
+ }
249
266
  }
250
267
  c.tsw.WriteLiterally(" ")
251
268
  tokStr, ok := TokenToTs(exp.Op)
@@ -258,7 +275,7 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
258
275
  }
259
276
 
260
277
  // Check if this is a pointer comparison (non-nil)
261
- // Compare the box objects directly using === or !==
278
+ // Compare the varRef objects directly using === or !==
262
279
  if c.isPointerComparison(exp) {
263
280
  c.tsw.WriteLiterally("(") // Wrap comparison
264
281
  if err := c.WriteValueExpr(exp.X); err != nil {
@@ -284,6 +301,50 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
284
301
  return nil
285
302
  }
286
303
 
304
+ // Check for Duration arithmetic operations (multiplication)
305
+ if exp.Op == token.MUL && c.pkg != nil && c.pkg.TypesInfo != nil {
306
+ leftType := c.pkg.TypesInfo.TypeOf(exp.X)
307
+ rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
308
+
309
+ // Check if left operand is a Duration type (from time package)
310
+ if leftType != nil {
311
+ if namedType, ok := leftType.(*types.Named); ok {
312
+ if namedType.Obj().Pkg() != nil && namedType.Obj().Pkg().Path() == "time" && namedType.Obj().Name() == "Duration" {
313
+ // Duration * number -> Duration.multiply(duration, number)
314
+ c.tsw.WriteLiterally("$.multiplyDuration(")
315
+ if err := c.WriteValueExpr(exp.X); err != nil {
316
+ return fmt.Errorf("failed to write Duration in multiplication: %w", err)
317
+ }
318
+ c.tsw.WriteLiterally(", ")
319
+ if err := c.WriteValueExpr(exp.Y); err != nil {
320
+ return fmt.Errorf("failed to write multiplier in Duration multiplication: %w", err)
321
+ }
322
+ c.tsw.WriteLiterally(")")
323
+ return nil
324
+ }
325
+ }
326
+ }
327
+
328
+ // Check if right operand is a Duration type (number * Duration)
329
+ if rightType != nil {
330
+ if namedType, ok := rightType.(*types.Named); ok {
331
+ if namedType.Obj().Pkg() != nil && namedType.Obj().Pkg().Path() == "time" && namedType.Obj().Name() == "Duration" {
332
+ // number * Duration -> Duration.multiply(duration, number)
333
+ c.tsw.WriteLiterally("$.multiplyDuration(")
334
+ if err := c.WriteValueExpr(exp.Y); err != nil {
335
+ return fmt.Errorf("failed to write Duration in multiplication: %w", err)
336
+ }
337
+ c.tsw.WriteLiterally(", ")
338
+ if err := c.WriteValueExpr(exp.X); err != nil {
339
+ return fmt.Errorf("failed to write multiplier in Duration multiplication: %w", err)
340
+ }
341
+ c.tsw.WriteLiterally(")")
342
+ return nil
343
+ }
344
+ }
345
+ }
346
+ }
347
+
287
348
  // Check if the operator is a bitwise operator
288
349
  isBitwise := false
289
350
  switch exp.Op {
@@ -334,11 +395,11 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
334
395
  // It handles several unary operations:
335
396
  // - Channel receive (`<-ch`): Becomes `await ch.receive()`.
336
397
  // - Address-of (`&var`):
337
- // - If `var` is a boxed variable (its address was taken), `&var` evaluates
338
- // to the box itself (i.e., `varName` in TypeScript, which holds the box).
339
- // - Otherwise (e.g., `&unboxedVar`, `&MyStruct{}`, `&FuncCall()`), it evaluates
398
+ // - If `var` is a varrefed variable (its address was taken), `&var` evaluates
399
+ // to the varRef itself (i.e., `varName` in TypeScript, which holds the varRef).
400
+ // - Otherwise (e.g., `&unvarrefedVar`, `&MyStruct{}`, `&FuncCall()`), it evaluates
340
401
  // the operand `var`. The resulting TypeScript value (e.g., a new object instance)
341
- // acts as the "pointer". Boxing decisions for such pointers are handled at
402
+ // acts as the "pointer". VarRefing decisions for such pointers are handled at
342
403
  // the assignment site.
343
404
  // - Other unary operators (`+`, `-`, `!`, `^`): Mapped to their TypeScript
344
405
  // equivalents (e.g., `+`, `-`, `!`, `~` for bitwise NOT). Parentheses are added
@@ -359,26 +420,26 @@ func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
359
420
  }
360
421
 
361
422
  if exp.Op == token.AND { // Address-of operator (&)
362
- // If the operand is an identifier for a variable that is boxed,
363
- // the result of & is the box itself.
423
+ // If the operand is an identifier for a variable that is varrefed,
424
+ // the result of & is the varRef itself.
364
425
  if ident, ok := exp.X.(*ast.Ident); ok {
365
426
  var obj types.Object
366
427
  obj = c.pkg.TypesInfo.Uses[ident]
367
428
  if obj == nil {
368
429
  obj = c.pkg.TypesInfo.Defs[ident]
369
430
  }
370
- if obj != nil && c.analysis.NeedsBoxed(obj) {
371
- // &boxedVar -> boxedVar (the box itself)
372
- c.tsw.WriteLiterally(ident.Name) // Write the identifier name (which holds the box)
431
+ if obj != nil && c.analysis.NeedsVarRef(obj) {
432
+ // &varRefVar -> varRefVar (the variable reference itself)
433
+ c.tsw.WriteLiterally(ident.Name) // Write the identifier name (which holds the variable reference)
373
434
  return nil
374
435
  }
375
436
  }
376
437
 
377
- // Otherwise (&unboxedVar, &CompositeLit{}, &FuncCall(), etc.),
438
+ // Otherwise (&unvarrefedVar, &CompositeLit{}, &FuncCall(), etc.),
378
439
  // the address-of operator in Go, when used to create a pointer,
379
440
  // translates to simply evaluating the operand in TypeScript.
380
441
  // The resulting value (e.g., a new object instance) acts as the "pointer".
381
- // Boxing decisions are handled at the assignment site based on the LHS variable.
442
+ // VarRefing decisions are handled at the assignment site based on the LHS variable.
382
443
  if err := c.WriteValueExpr(exp.X); err != nil {
383
444
  return fmt.Errorf("failed to write &-operand: %w", err)
384
445
  }
@@ -82,16 +82,17 @@ func GoBuiltinToTypescript(typeName string) (string, bool) {
82
82
  // in their respective expression/statement writers and might not be directly mapped here.
83
83
  // Bitwise AND NOT (`&^=`) is also mapped but may require specific runtime support if not directly translatable.
84
84
  var tokenMap = map[token.Token]string{
85
- token.ADD: "+",
86
- token.SUB: "-",
87
- token.MUL: "*",
88
- token.QUO: "/",
89
- token.REM: "%",
90
- token.AND: "&",
91
- token.OR: "|",
92
- token.XOR: "^",
93
- token.SHL: "<<",
94
- token.SHR: ">>",
85
+ token.ADD: "+",
86
+ token.SUB: "-",
87
+ token.MUL: "*",
88
+ token.QUO: "/",
89
+ token.REM: "%",
90
+ token.AND: "&",
91
+ token.OR: "|",
92
+ token.XOR: "^",
93
+ token.SHL: "<<",
94
+ token.SHR: ">>",
95
+ token.AND_NOT: "& ~", // &^ operator: bitwise AND NOT
95
96
 
96
97
  token.ADD_ASSIGN: "+=",
97
98
  token.SUB_ASSIGN: "-=",
@@ -12,7 +12,7 @@ import (
12
12
  // It handles the generation of:
13
13
  // - The class declaration.
14
14
  // - Getters and setters for all fields (both direct and embedded).
15
- // - The internal `_fields` property, which stores field values in `$.Box` containers
15
+ // - The internal `_fields` property, which stores field values in `$.VarRef` containers
16
16
  // to maintain Go's value semantics.
17
17
  // - A constructor that initializes the `_fields` and allows partial initialization.
18
18
  // - A `clone` method for creating a deep copy of the struct instance.
@@ -83,7 +83,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
83
83
  fieldKeyName = field.Name()
84
84
  }
85
85
  fieldTsType := c.getTypeString(field.Type())
86
- c.tsw.WriteLinef("%s: $.Box<%s>;", fieldKeyName, fieldTsType)
86
+ c.tsw.WriteLinef("%s: $.VarRef<%s>;", fieldKeyName, fieldTsType)
87
87
  }
88
88
  c.tsw.Indent(-1)
89
89
  c.tsw.WriteLine("}")
@@ -111,7 +111,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
111
111
  fieldKeyName = field.Name()
112
112
  }
113
113
 
114
- c.writeBoxedFieldInitializer(fieldKeyName, fieldType, field.Anonymous())
114
+ c.writeVarRefedFieldInitializer(fieldKeyName, fieldType, field.Anonymous())
115
115
 
116
116
  if i < numFields-1 {
117
117
  c.tsw.WriteLine(",")
@@ -12,13 +12,12 @@ import (
12
12
  // declarations.
13
13
  //
14
14
  // For single variable declarations (`var x T = val` or `var x = val` or `var x T`):
15
- // - It determines if the variable `x` needs to be boxed (e.g., if its address is taken)
16
- // using `c.analysis.NeedsBoxed(obj)`.
17
- // - If boxed: `let x: $.Box<T_ts> = $.box(initializer_ts_or_zero_ts);`
18
- // The type annotation is `$.Box<T_ts>`, and the initializer is wrapped in `$.box()`.
19
- // - If not boxed: `let x: T_ts = initializer_ts_or_zero_ts;`
20
- // The type annotation is `T_ts`. If the initializer is `&unboxedVar`, it becomes `$.box(unboxedVar_ts)`.
21
- // If the RHS is a struct value, `.clone()` is applied to maintain Go's value semantics.
15
+ // - It determines if the variable `x` needs to be varrefed (e.g., if its address is taken)
16
+ // using `c.analysis.NeedsVarRef(obj)`.
17
+ // - If variable referenced: `let x: $.VarRef<T_ts> = $.varRef(initializer_ts_or_zero_ts);`
18
+ // The type annotation is `$.VarRef<T_ts>`, and the initializer is wrapped in `$.varRef()`.
19
+ // - If not variable referenced: `let x: T_ts = initializer_ts_or_zero_ts;`
20
+ // The type annotation is `T_ts`. If the initializer is `&unvarrefedVar`, it becomes `$.varRef(unvarrefedVar_ts)`.
22
21
  // - If no initializer is provided, the TypeScript zero value (from `WriteZeroValueForType`)
23
22
  // is used.
24
23
  // - Type `T` (or `T_ts`) is obtained from `obj.Type()` and translated via `WriteGoType`.
@@ -48,7 +47,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
48
47
  }
49
48
 
50
49
  goType := obj.Type()
51
- needsBox := c.analysis.NeedsBoxed(obj) // Check if address is taken
50
+ needsVarRef := c.analysis.NeedsVarRef(obj) // Check if address is taken
52
51
 
53
52
  hasInitializer := len(a.Values) > 0
54
53
  var initializerExpr ast.Expr
@@ -86,17 +85,64 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
86
85
  c.tsw.WriteLiterally("let ")
87
86
  c.tsw.WriteLiterally(name.Name)
88
87
 
89
- if !isSliceConversion {
88
+ // Write type annotation if:
89
+ // 1. Not a slice conversion (normal case), OR
90
+ // 2. Is a slice conversion but needs varRefing (we need explicit type for $.varRef())
91
+ if !isSliceConversion || needsVarRef {
90
92
  c.tsw.WriteLiterally(": ")
91
93
  // Write type annotation
92
- if needsBox {
93
- // If boxed, the variable holds Box<OriginalGoType>
94
- c.tsw.WriteLiterally("$.Box<")
95
- c.WriteGoType(goType, GoTypeContextGeneral) // Write the original Go type T
94
+ if needsVarRef {
95
+ // If varrefed, the variable holds VarRef<OriginalGoType>
96
+ c.tsw.WriteLiterally("$.VarRef<")
97
+
98
+ // Special case: if this is a slice conversion from an array type,
99
+ // we should use the slice type instead of the array type
100
+ if isSliceConversion {
101
+ if arrayType, isArray := goType.Underlying().(*types.Array); isArray {
102
+ // Convert [N]T to $.Slice<T>
103
+ c.tsw.WriteLiterally("$.Slice<")
104
+ c.WriteGoType(arrayType.Elem(), GoTypeContextGeneral)
105
+ c.tsw.WriteLiterally(">")
106
+ } else {
107
+ // For slice types, write as-is (already $.Slice<T>)
108
+ c.WriteGoType(goType, GoTypeContextGeneral)
109
+ }
110
+ } else {
111
+ c.WriteGoType(goType, GoTypeContextGeneral) // Write the original Go type T
112
+ }
96
113
  c.tsw.WriteLiterally(">")
97
114
  } else {
98
- // If not boxed, the variable holds the translated Go type directly
99
- c.WriteGoType(goType, GoTypeContextGeneral)
115
+ // If not varrefed, the variable holds the translated Go type directly
116
+ // Custom logic for non-var-ref'd pointers to structs/interfaces.
117
+ if ptrType, isPtr := goType.(*types.Pointer); isPtr {
118
+ elemType := ptrType.Elem()
119
+ actualElemType := elemType.Underlying() // Get the true underlying type (e.g., struct, interface, basic)
120
+
121
+ isStruct := false
122
+ if _, ok := actualElemType.(*types.Struct); ok {
123
+ isStruct = true
124
+ }
125
+
126
+ isInterface := false
127
+ if _, ok := actualElemType.(*types.Interface); ok {
128
+ isInterface = true
129
+ }
130
+
131
+ if isStruct || isInterface {
132
+ // For non-var-ref'd pointers to structs or interfaces,
133
+ // the type is T | null, not $.VarRef<T> | null.
134
+ c.WriteGoType(elemType, GoTypeContextGeneral) // Write the element type itself (e.g., MyStruct)
135
+ c.tsw.WriteLiterally(" | null")
136
+ } else {
137
+ // For other pointer types (e.g., *int, *string, *[]int, **MyStruct),
138
+ // or pointers to types that are not structs/interfaces,
139
+ // use the standard pointer type translation.
140
+ c.WriteGoType(goType, GoTypeContextGeneral)
141
+ }
142
+ } else {
143
+ // Not a pointer type, write as is.
144
+ c.WriteGoType(goType, GoTypeContextGeneral)
145
+ }
100
146
  }
101
147
  }
102
148
 
@@ -121,41 +167,41 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
121
167
  }
122
168
  }
123
169
 
124
- if needsBox {
125
- // Boxed variable: let v: Box<T> = $.box(init_or_zero);
126
- c.tsw.WriteLiterally("$.box(")
170
+ if needsVarRef {
171
+ // VarRef variable: let v: VarRef<T> = $.varRef(init_or_zero);
172
+ c.tsw.WriteLiterally("$.varRef(")
127
173
  if hasInitializer {
128
174
  // Write the compiled initializer expression normally
129
175
  if err := c.WriteValueExpr(initializerExpr); err != nil {
130
176
  return err
131
177
  }
132
178
  } else {
133
- // No initializer, box the zero value
179
+ // No initializer, varRef the zero value
134
180
  c.WriteZeroValueForType(goType)
135
181
  }
136
182
  c.tsw.WriteLiterally(")")
137
183
  } else {
138
- // Unboxed variable: let v: T = init_or_zero;
184
+ // Unvarrefed variable: let v: T = init_or_zero;
139
185
  if hasInitializer {
140
- // Handle &v initializer specifically for unboxed variables
186
+ // Handle &v initializer specifically for unvarrefed variables
141
187
  if unaryExpr, isUnary := initializerExpr.(*ast.UnaryExpr); isUnary && unaryExpr.Op == token.AND {
142
188
  // Initializer is &v
143
- // Check if v is boxed
144
- needsBoxOperand := false
189
+ // Check if v is varrefed
190
+ needsVarRefOperand := false
145
191
  unaryExprXIdent, ok := unaryExpr.X.(*ast.Ident)
146
192
  if ok {
147
193
  innerObj := c.pkg.TypesInfo.Uses[unaryExprXIdent]
148
- needsBoxOperand = innerObj != nil && c.analysis.NeedsBoxed(innerObj)
194
+ needsVarRefOperand = innerObj != nil && c.analysis.NeedsVarRef(innerObj)
149
195
  }
150
196
 
151
- // If v is boxed, assign the box itself (v)
152
- // If v is not boxed, assign $.box(v)
153
- if needsBoxOperand {
197
+ // If v is varrefed, assign the varRef itself (v)
198
+ // If v is not varrefed, assign $.varRef(v)
199
+ if needsVarRefOperand {
154
200
  // special handling: do not write .value here.
155
201
  c.WriteIdent(unaryExprXIdent, false)
156
202
  } else {
157
- // &unboxedVar -> $.box(unboxedVar)
158
- c.tsw.WriteLiterally("$.box(")
203
+ // &unvarrefedVar -> $.varRef(unvarrefedVar)
204
+ c.tsw.WriteLiterally("$.varRef(")
159
205
  if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Write 'v'
160
206
  return err
161
207
  }
package/compiler/spec.go CHANGED
@@ -78,9 +78,9 @@ func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Typ
78
78
  c.tsw.WriteLine("")
79
79
  }
80
80
 
81
- func (c *GoToTSCompiler) writeBoxedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
81
+ func (c *GoToTSCompiler) writeVarRefedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
82
82
  c.tsw.WriteLiterally(fieldName)
83
- c.tsw.WriteLiterally(": $.box(")
83
+ c.tsw.WriteLiterally(": $.varRef(")
84
84
 
85
85
  if isEmbedded {
86
86
  if _, isPtr := fieldType.(*types.Pointer); isPtr {
@@ -112,7 +112,7 @@ func (c *GoToTSCompiler) writeBoxedFieldInitializer(fieldName string, fieldType
112
112
 
113
113
  func (c *GoToTSCompiler) writeClonedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
114
114
  c.tsw.WriteLiterally(fieldName)
115
- c.tsw.WriteLiterally(": $.box(")
115
+ c.tsw.WriteLiterally(": $.varRef(")
116
116
 
117
117
  if isEmbedded {
118
118
  isPointerToStruct := false
@@ -183,6 +183,12 @@ func (c *GoToTSCompiler) WriteInterfaceTypeSpec(a *ast.TypeSpec, t *ast.Interfac
183
183
  if err := c.WriteValueExpr(a.Name); err != nil {
184
184
  return err
185
185
  }
186
+
187
+ // Write type parameters if present (for generics)
188
+ if a.TypeParams != nil {
189
+ c.WriteTypeParameters(a.TypeParams)
190
+ }
191
+
186
192
  c.tsw.WriteLiterally(" = ")
187
193
  // Get the types.Interface from the ast.InterfaceType.
188
194
  // For an interface definition like `type MyInterface interface { M() }`,
@@ -33,7 +33,7 @@ import (
33
33
  // - Uses `writeAssignmentCore` which handles:
34
34
  // - Blank identifier `_` on LHS (evaluates RHS for side effects).
35
35
  // - Assignment to dereferenced pointer `*p = val` -> `p_ts!.value = val_ts`.
36
- // - Short declaration `x := y`: `let x = y_ts;`. If `x` is boxed, `let x: $.Box<T> = $.box(y_ts);`.
36
+ // - Short declaration `x := y`: `let x = y_ts;`. If `x` is variable referenced, `let x: $.VarRef<T> = $.varRef(y_ts);`.
37
37
  // - Regular assignment `x = y`, including compound assignments like `x += y`.
38
38
  // - Assignment to map index `m[k] = v` using `$.mapSet`.
39
39
  // - Struct value assignment `s1 = s2` becomes `s1 = s2.clone()` if `s2` is a struct.
@@ -44,7 +44,7 @@ import (
44
44
  // The function ensures that the number of LHS and RHS expressions matches for
45
45
  // most cases, erroring if they don't, except for specifically handled patterns
46
46
  // like multi-assign from single call or discarded channel receive.
47
- // It correctly applies `let` for `:=` (define) tokens and handles boxing and
47
+ // It correctly applies `let` for `:=` (define) tokens and handles varRefing and
48
48
  // cloning semantics based on type information and analysis.
49
49
  func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
50
50
  // writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
@@ -147,6 +147,10 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
147
147
  hasSelectors = true
148
148
  break
149
149
  }
150
+ if _, ok := lhsExpr.(*ast.StarExpr); ok {
151
+ hasSelectors = true
152
+ break
153
+ }
150
154
  }
151
155
 
152
156
  // If we have selector expressions, we need to ensure variables are initialized
@@ -176,6 +180,21 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
176
180
  if err := c.WriteValueExpr(selectorExpr); err != nil {
177
181
  return fmt.Errorf("failed to write selector expression in LHS: %w", err)
178
182
  }
183
+ } else if starExpr, ok := lhsExpr.(*ast.StarExpr); ok {
184
+ // Handle pointer dereference assignment: *p = value becomes p!.value = value
185
+ // Write the pointer variable directly without using WriteValueExpr
186
+ // because we don't want automatic .value access here
187
+ switch operand := starExpr.X.(type) {
188
+ case *ast.Ident:
189
+ // Write identifier without .value access
190
+ c.WriteIdent(operand, false)
191
+ default:
192
+ // For other expressions, use WriteValueExpr
193
+ if err := c.WriteValueExpr(starExpr.X); err != nil {
194
+ return fmt.Errorf("failed to write star expression X in LHS: %w", err)
195
+ }
196
+ }
197
+ c.tsw.WriteLiterally("!.value")
179
198
  } else {
180
199
  return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
181
200
  }
@@ -211,6 +230,21 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
211
230
  if err := c.WriteValueExpr(selectorExpr); err != nil {
212
231
  return fmt.Errorf("failed to write selector expression in LHS: %w", err)
213
232
  }
233
+ } else if starExpr, ok := lhsExpr.(*ast.StarExpr); ok {
234
+ // Handle pointer dereference in destructuring: *p becomes p!.value
235
+ // Write the pointer variable directly without using WriteValueExpr
236
+ // because we don't want automatic .value access here
237
+ switch operand := starExpr.X.(type) {
238
+ case *ast.Ident:
239
+ // Write identifier without .value access
240
+ c.WriteIdent(operand, false)
241
+ default:
242
+ // For other expressions, use WriteValueExpr
243
+ if err := c.WriteValueExpr(starExpr.X); err != nil {
244
+ return fmt.Errorf("failed to write star expression X in destructuring: %w", err)
245
+ }
246
+ }
247
+ c.tsw.WriteLiterally("!.value")
214
248
  } else {
215
249
  // Should not happen for valid Go code in this context, but handle defensively
216
250
  return errors.Errorf("unhandled LHS expression in destructuring: %T", lhsExpr)