goscript 0.0.22 → 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 (66) hide show
  1. package/README.md +1 -1
  2. package/cmd/goscript/cmd_compile.go +3 -3
  3. package/compiler/analysis.go +302 -182
  4. package/compiler/analysis_test.go +220 -0
  5. package/compiler/assignment.go +42 -43
  6. package/compiler/builtin_test.go +102 -0
  7. package/compiler/compiler.go +117 -29
  8. package/compiler/compiler_test.go +36 -8
  9. package/compiler/composite-lit.go +133 -53
  10. package/compiler/config.go +7 -3
  11. package/compiler/config_test.go +6 -33
  12. package/compiler/decl.go +36 -0
  13. package/compiler/expr-call.go +116 -60
  14. package/compiler/expr-selector.go +88 -43
  15. package/compiler/expr-star.go +57 -65
  16. package/compiler/expr-type.go +132 -5
  17. package/compiler/expr-value.go +8 -38
  18. package/compiler/expr.go +326 -30
  19. package/compiler/field.go +3 -3
  20. package/compiler/lit.go +34 -2
  21. package/compiler/primitive.go +19 -12
  22. package/compiler/spec-struct.go +140 -9
  23. package/compiler/spec-value.go +119 -41
  24. package/compiler/spec.go +21 -6
  25. package/compiler/stmt-assign.go +65 -3
  26. package/compiler/stmt-for.go +11 -0
  27. package/compiler/stmt-range.go +119 -11
  28. package/compiler/stmt-select.go +211 -0
  29. package/compiler/stmt-type-switch.go +147 -0
  30. package/compiler/stmt.go +175 -238
  31. package/compiler/type-assert.go +125 -379
  32. package/compiler/type.go +216 -129
  33. package/dist/gs/builtin/builtin.js +37 -0
  34. package/dist/gs/builtin/builtin.js.map +1 -0
  35. package/dist/gs/builtin/channel.js +471 -0
  36. package/dist/gs/builtin/channel.js.map +1 -0
  37. package/dist/gs/builtin/defer.js +54 -0
  38. package/dist/gs/builtin/defer.js.map +1 -0
  39. package/dist/gs/builtin/io.js +15 -0
  40. package/dist/gs/builtin/io.js.map +1 -0
  41. package/dist/gs/builtin/map.js +44 -0
  42. package/dist/gs/builtin/map.js.map +1 -0
  43. package/dist/gs/builtin/slice.js +799 -0
  44. package/dist/gs/builtin/slice.js.map +1 -0
  45. package/dist/gs/builtin/type.js +745 -0
  46. package/dist/gs/builtin/type.js.map +1 -0
  47. package/dist/gs/builtin/varRef.js +14 -0
  48. package/dist/gs/builtin/varRef.js.map +1 -0
  49. package/dist/gs/context/context.js +55 -0
  50. package/dist/gs/context/context.js.map +1 -0
  51. package/dist/gs/context/index.js +2 -0
  52. package/dist/gs/context/index.js.map +1 -0
  53. package/dist/gs/runtime/index.js +2 -0
  54. package/dist/gs/runtime/index.js.map +1 -0
  55. package/dist/gs/runtime/runtime.js +158 -0
  56. package/dist/gs/runtime/runtime.js.map +1 -0
  57. package/dist/gs/time/index.js +2 -0
  58. package/dist/gs/time/index.js.map +1 -0
  59. package/dist/gs/time/time.js +115 -0
  60. package/dist/gs/time/time.js.map +1 -0
  61. package/package.json +7 -6
  62. package/builtin/builtin.go +0 -11
  63. package/builtin/builtin.ts +0 -2379
  64. package/dist/builtin/builtin.d.ts +0 -513
  65. package/dist/builtin/builtin.js +0 -1686
  66. package/dist/builtin/builtin.js.map +0 -1
@@ -13,13 +13,17 @@ type Config struct {
13
13
 
14
14
  // Dir is the working directory for the compiler. If empty, uses the current working directory.
15
15
  Dir string
16
- // OutputPathRoot is the output path root.
17
- OutputPathRoot string
16
+ // OutputPath is the output path root.
17
+ OutputPath string
18
18
  // BuildFlags are the Go build flags (tags) to use during analysis.
19
19
  BuildFlags []string
20
20
  // AllDependencies controls whether to compile all dependencies of the requested packages.
21
21
  // If true, all dependencies will be compiled; if false, only the requested packages are compiled.
22
22
  AllDependencies bool
23
+ // DisableEmitBuiltin controls whether to emit builtin packages when they are referenced.
24
+ // If true, builtin packages will not be emitted; if false, they will be emitted if referenced.
25
+ // Default is false (emit builtin packages).
26
+ DisableEmitBuiltin bool
23
27
  }
24
28
 
25
29
  // Validate checks the config.
@@ -30,7 +34,7 @@ func (c *Config) Validate() error {
30
34
  if c.fset == nil {
31
35
  c.fset = token.NewFileSet()
32
36
  }
33
- if c.OutputPathRoot == "" {
37
+ if c.OutputPath == "" {
34
38
  return errors.New("output path root must be specified")
35
39
  }
36
40
  return nil
@@ -1,7 +1,6 @@
1
1
  package compiler
2
2
 
3
3
  import (
4
- "reflect"
5
4
  "testing"
6
5
  )
7
6
 
@@ -15,9 +14,9 @@ func TestConfigValidate(t *testing.T) {
15
14
  {
16
15
  name: "valid config",
17
16
  config: &Config{
18
- Dir: "/some/dir",
19
- OutputPathRoot: "/output/path",
20
- BuildFlags: []string{"-tags", "sometag"},
17
+ Dir: "/some/dir",
18
+ OutputPath: "/output/path",
19
+ BuildFlags: []string{"-tags", "sometag"},
21
20
  },
22
21
  wantErr: false,
23
22
  },
@@ -33,9 +32,9 @@ func TestConfigValidate(t *testing.T) {
33
32
  {
34
33
  name: "nil fset gets initialized",
35
34
  config: &Config{
36
- fset: nil,
37
- Dir: "/some/dir",
38
- OutputPathRoot: "/output/path",
35
+ fset: nil,
36
+ Dir: "/some/dir",
37
+ OutputPath: "/output/path",
39
38
  },
40
39
  wantErr: false,
41
40
  },
@@ -61,29 +60,3 @@ func TestConfigValidate(t *testing.T) {
61
60
  })
62
61
  }
63
62
  }
64
-
65
- func TestConfigFields(t *testing.T) {
66
- // Verify that Config has the expected fields
67
- config := Config{}
68
- configType := reflect.TypeOf(config)
69
-
70
- expectedFields := map[string]string{
71
- "fset": "*token.FileSet",
72
- "Dir": "string",
73
- "OutputPathRoot": "string",
74
- "BuildFlags": "[]string",
75
- }
76
-
77
- for fieldName, expectedType := range expectedFields {
78
- field, exists := configType.FieldByName(fieldName)
79
- if !exists {
80
- t.Errorf("Expected Config to have field %s", fieldName)
81
- continue
82
- }
83
-
84
- actualType := field.Type.String()
85
- if actualType != expectedType {
86
- t.Errorf("Field %s has type %s, expected %s", fieldName, actualType, expectedType)
87
- }
88
- }
89
- }
package/compiler/decl.go CHANGED
@@ -88,14 +88,50 @@ func (c *GoToTSCompiler) WriteFuncDeclAsFunction(decl *ast.FuncDecl) error {
88
88
  return fmt.Errorf("failed to write function name: %w", err)
89
89
  }
90
90
 
91
+ // Write type parameters if present
92
+ if decl.Type.TypeParams != nil {
93
+ c.WriteTypeParameters(decl.Type.TypeParams)
94
+ }
95
+
91
96
  // WriteFuncType needs to be aware if the function is async
92
97
  c.WriteFuncType(decl.Type, isAsync) // Write signature (params, return type)
93
98
  c.tsw.WriteLiterally(" ")
94
99
 
100
+ hasNamedReturns := false
101
+ if decl.Type.Results != nil {
102
+ for _, field := range decl.Type.Results.List {
103
+ if len(field.Names) > 0 {
104
+ hasNamedReturns = true
105
+ break
106
+ }
107
+ }
108
+ }
109
+
110
+ if hasNamedReturns {
111
+ c.tsw.WriteLine("{")
112
+ c.tsw.Indent(1)
113
+
114
+ // Declare named return variables and initialize them to their zero values
115
+ for _, field := range decl.Type.Results.List {
116
+ for _, name := range field.Names {
117
+ c.tsw.WriteLiterallyf("let %s: ", name.Name)
118
+ c.WriteTypeExpr(field.Type)
119
+ c.tsw.WriteLiterally(" = ")
120
+ c.WriteZeroValueForType(c.pkg.TypesInfo.TypeOf(field.Type))
121
+ c.tsw.WriteLine("")
122
+ }
123
+ }
124
+ }
125
+
95
126
  if err := c.WriteStmt(decl.Body); err != nil {
96
127
  return fmt.Errorf("failed to write function body: %w", err)
97
128
  }
98
129
 
130
+ if hasNamedReturns {
131
+ c.tsw.Indent(-1)
132
+ c.tsw.WriteLine("}")
133
+ }
134
+
99
135
  return nil
100
136
  }
101
137
 
@@ -13,15 +13,19 @@ import (
13
13
  // into its TypeScript equivalent.
14
14
  // It handles several Go built-in functions specially:
15
15
  // - `println(...)` becomes `console.log(...)`.
16
+ // - `panic(...)` becomes `$.panic(...)`.
16
17
  // - `len(arg)` becomes `$.len(arg)`.
17
18
  // - `cap(arg)` becomes `$.cap(arg)`.
18
19
  // - `delete(m, k)` becomes `$.deleteMapEntry(m, k)`.
19
20
  // - `make(chan T, size)` becomes `$.makeChannel<T_ts>(size, zeroValueForT)`.
20
21
  // - `make(map[K]V)` becomes `$.makeMap<K_ts, V_ts>()`.
21
22
  // - `make([]T, len, cap)` becomes `$.makeSlice<T_ts>(len, cap)`.
23
+ // - `make([]byte, len, cap)` becomes `new Uint8Array(len)`.
22
24
  // - `string(runeVal)` becomes `String.fromCharCode(runeVal)`.
23
- // - `string([]runeVal)` or `string([]byteVal)` becomes `$.runesToString(sliceVal)`.
24
- // - `[]rune(stringVal)` becomes `$.stringToRunes(stringVal)`.
25
+ // - `string([]runeVal)` becomes `$.runesToString(sliceVal)`.
26
+ // - `string([]byteVal)` becomes `$.bytesToString(sliceVal)`.
27
+ // - `[]rune(stringVal)` becomes `$.stringToRunes(stringVal)“.
28
+ // - `[]byte(stringVal)` becomes `$.stringToBytes(stringVal)`.
25
29
  // - `close(ch)` becomes `ch.close()`.
26
30
  // - `append(slice, elems...)` becomes `$.append(slice, elems...)`.
27
31
  // - `byte(val)` becomes `$.byte(val)`.
@@ -70,66 +74,56 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
70
74
  }
71
75
  }
72
76
  }
77
+ // Check if it's a []byte type and the argument is a string
78
+ if eltIdent, ok := arrayType.Elt.(*ast.Ident); ok && eltIdent.Name == "byte" && arrayType.Len == nil {
79
+ if len(exp.Args) == 1 {
80
+ arg := exp.Args[0]
81
+ // Ensure TypesInfo is available and the argument type can be determined
82
+ if tv, typeOk := c.pkg.TypesInfo.Types[arg]; typeOk && tv.Type != nil {
83
+ if basicArgType, isBasic := tv.Type.Underlying().(*types.Basic); isBasic && (basicArgType.Info()&types.IsString) != 0 {
84
+ c.tsw.WriteLiterally("$.stringToBytes(")
85
+ if err := c.WriteValueExpr(arg); err != nil {
86
+ return fmt.Errorf("failed to write argument for []byte(string) conversion: %w", err)
87
+ }
88
+ c.tsw.WriteLiterally(")")
89
+ return nil // Handled []byte(string)
90
+ }
91
+ }
92
+ }
93
+ }
73
94
  }
74
95
 
75
96
  if funIdent, funIsIdent := expFun.(*ast.Ident); funIsIdent {
76
97
  switch funIdent.String() {
98
+ case "panic":
99
+ c.tsw.WriteLiterally("$.panic")
77
100
  case "println":
78
- c.tsw.WriteLiterally("console.log(")
79
- for i, arg := range exp.Args {
80
- if i != 0 {
81
- c.tsw.WriteLiterally(", ")
82
- }
83
- if err := c.WriteValueExpr(arg); err != nil {
84
- return err
85
- }
86
- }
87
- c.tsw.WriteLiterally(")")
88
- return nil
101
+ c.tsw.WriteLiterally("console.log")
89
102
  case "len":
90
103
  // Translate len(arg) to $.len(arg)
91
- if len(exp.Args) == 1 {
92
- c.tsw.WriteLiterally("$.len(")
93
- if err := c.WriteValueExpr(exp.Args[0]); err != nil {
94
- return err
95
- }
96
- c.tsw.WriteLiterally(")")
97
- return nil // Handled len
104
+ if len(exp.Args) != 1 {
105
+ return errors.Errorf("unhandled len call with incorrect number of arguments: %d != 1", len(exp.Args))
98
106
  }
99
- return errors.New("unhandled len call with incorrect number of arguments")
107
+ c.tsw.WriteLiterally("$.len")
100
108
  case "cap":
101
109
  // Translate cap(arg) to $.cap(arg)
102
- if len(exp.Args) == 1 {
103
- c.tsw.WriteLiterally("$.cap(")
104
- if err := c.WriteValueExpr(exp.Args[0]); err != nil {
105
- return err
106
- }
107
- c.tsw.WriteLiterally(")")
108
- return nil // Handled cap
110
+ if len(exp.Args) != 1 {
111
+ return errors.Errorf("unhandled cap call with incorrect number of arguments: %d != 1", len(exp.Args))
109
112
  }
110
- return errors.New("unhandled cap call with incorrect number of arguments")
113
+ c.tsw.WriteLiterally("$.cap")
111
114
  case "delete":
112
115
  // Translate delete(map, key) to $.deleteMapEntry(map, key)
113
- if len(exp.Args) == 2 {
114
- c.tsw.WriteLiterally("$.deleteMapEntry(")
115
- if err := c.WriteValueExpr(exp.Args[0]); err != nil { // Map
116
- return err
117
- }
118
- c.tsw.WriteLiterally(", ")
119
- if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Key
120
- return err
121
- }
122
- c.tsw.WriteLiterally(")")
123
- return nil // Handled delete
116
+ if len(exp.Args) != 2 {
117
+ return errors.Errorf("unhandled delete call with incorrect number of arguments: %d != 2", len(exp.Args))
124
118
  }
125
- return errors.New("unhandled delete call with incorrect number of arguments")
119
+ c.tsw.WriteLiterally("$.deleteMapEntry")
126
120
  case "make":
127
121
  // First check if we have a channel type
128
122
  if typ := c.pkg.TypesInfo.TypeOf(exp.Args[0]); typ != nil {
129
123
  if chanType, ok := typ.Underlying().(*types.Chan); ok {
130
124
  // Handle channel creation: make(chan T, bufferSize) or make(chan T)
131
125
  c.tsw.WriteLiterally("$.makeChannel<")
132
- c.WriteGoType(chanType.Elem())
126
+ c.WriteGoType(chanType.Elem(), GoTypeContextGeneral)
133
127
  c.tsw.WriteLiterally(">(")
134
128
 
135
129
  // If buffer size is provided, add it
@@ -189,13 +183,30 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
189
183
  if sliceType == nil {
190
184
  return errors.New("could not get type information for slice in make call")
191
185
  }
192
- goElemType, ok := sliceType.Underlying().(*types.Slice)
186
+ goUnderlyingType, ok := sliceType.Underlying().(*types.Slice)
193
187
  if !ok {
194
188
  return errors.New("expected slice type for make call")
195
189
  }
190
+ goElemType := goUnderlyingType.Elem()
191
+
192
+ // Check if it's make([]byte, ...)
193
+ if basicElem, isBasic := goElemType.(*types.Basic); isBasic && basicElem.Kind() == types.Uint8 {
194
+ c.tsw.WriteLiterally("new Uint8Array(")
195
+ if len(exp.Args) >= 2 {
196
+ if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Length
197
+ return err
198
+ }
199
+ // Capacity argument for make([]byte, len, cap) is ignored for new Uint8Array(len)
200
+ } else {
201
+ // If no length is provided, default to 0
202
+ c.tsw.WriteLiterally("0")
203
+ }
204
+ c.tsw.WriteLiterally(")")
205
+ return nil // Handled make for []byte
206
+ }
196
207
 
197
208
  c.tsw.WriteLiterally("$.makeSlice<")
198
- c.WriteGoType(goElemType.Elem()) // Write the element type
209
+ c.WriteGoType(goElemType, GoTypeContextGeneral) // Write the element type
199
210
  c.tsw.WriteLiterally(">(")
200
211
 
201
212
  if len(exp.Args) >= 2 {
@@ -253,7 +264,16 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
253
264
  // Handle direct string(int32) conversion
254
265
  // This assumes 'rune' is int32
255
266
  if tv, ok := c.pkg.TypesInfo.Types[arg]; ok {
256
- if basic, isBasic := tv.Type.Underlying().(*types.Basic); isBasic && basic.Kind() == types.Int32 {
267
+ // Case 3a: Argument is already a string - no-op
268
+ if basic, isBasic := tv.Type.Underlying().(*types.Basic); isBasic && basic.Kind() == types.String {
269
+ // Translate string(stringValue) to stringValue (no-op)
270
+ if err := c.WriteValueExpr(arg); err != nil {
271
+ return fmt.Errorf("failed to write argument for string(string) no-op conversion: %w", err)
272
+ }
273
+ return nil // Handled string(string) no-op
274
+ }
275
+
276
+ if basic, isBasic := tv.Type.Underlying().(*types.Basic); isBasic && (basic.Kind() == types.Int32 || basic.Kind() == types.UntypedRune) {
257
277
  // Translate string(rune_val) to String.fromCharCode(rune_val)
258
278
  c.tsw.WriteLiterally("String.fromCharCode(")
259
279
  if err := c.WriteValueExpr(arg); err != nil {
@@ -266,18 +286,43 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
266
286
  // Case 3: Argument is a slice of runes or bytes string([]rune{...}) or string([]byte{...})
267
287
  if sliceType, isSlice := tv.Type.Underlying().(*types.Slice); isSlice {
268
288
  if basic, isBasic := sliceType.Elem().Underlying().(*types.Basic); isBasic {
269
- // Handle both runes (int32) and bytes (uint8)
270
- if basic.Kind() == types.Int32 || basic.Kind() == types.Uint8 {
271
- // Translate string([]rune) or string([]byte) to $.runesToString(...)
289
+ // Handle string([]byte)
290
+ if basic.Kind() == types.Uint8 {
291
+ c.tsw.WriteLiterally("$.bytesToString(")
292
+ if err := c.WriteValueExpr(arg); err != nil {
293
+ return fmt.Errorf("failed to write argument for string([]byte) conversion: %w", err)
294
+ }
295
+ c.tsw.WriteLiterally(")")
296
+ return nil // Handled string([]byte)
297
+ }
298
+ // Handle both runes (int32)
299
+ if basic.Kind() == types.Int32 {
300
+ // Translate string([]rune) to $.runesToString(...)
272
301
  c.tsw.WriteLiterally("$.runesToString(")
273
302
  if err := c.WriteValueExpr(arg); err != nil {
274
- return fmt.Errorf("failed to write argument for string([]rune/[]byte) conversion: %w", err)
303
+ return fmt.Errorf("failed to write argument for string([]rune) conversion: %w", err)
275
304
  }
276
305
  c.tsw.WriteLiterally(")")
277
- return nil // Handled string([]rune) or string([]byte)
306
+ return nil // Handled string([]rune)
278
307
  }
279
308
  }
280
309
  }
310
+
311
+ // Case 4: Argument is a generic type parameter (e.g., string | []byte)
312
+ if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
313
+ // Check if this is a []byte | string union constraint
314
+ constraint := typeParam.Constraint()
315
+ if constraint != nil {
316
+ // For now, assume any type parameter that could be string or []byte needs the helper
317
+ // This is a heuristic - in the future we could parse the constraint more precisely
318
+ c.tsw.WriteLiterally("$.genericBytesOrStringToString(")
319
+ if err := c.WriteValueExpr(arg); err != nil {
320
+ return fmt.Errorf("failed to write argument for string(generic) conversion: %w", err)
321
+ }
322
+ c.tsw.WriteLiterally(")")
323
+ return nil // Handled string(generic type parameter)
324
+ }
325
+ }
281
326
  }
282
327
  }
283
328
  // Return error for other unhandled string conversions
@@ -352,8 +397,10 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
352
397
  return fmt.Errorf("failed to write argument for type cast: %w", err)
353
398
  }
354
399
 
355
- // Then use the TypeScript "as" operator with the type name
356
- c.tsw.WriteLiterallyf(" as %s)", funIdent.String())
400
+ // Then use the TypeScript "as" operator with the mapped type name
401
+ c.tsw.WriteLiterally(" as ")
402
+ c.WriteGoType(typeName.Type(), GoTypeContextGeneral)
403
+ c.tsw.WriteLiterally(")")
357
404
  return nil // Handled non-function type cast
358
405
  }
359
406
  }
@@ -396,15 +443,24 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
396
443
  return nil // Handled regular function call
397
444
  }
398
445
  } else {
399
- // Not an identifier (e.g., method call on a value)
400
- if err := c.WriteValueExpr(expFun); err != nil {
401
- return fmt.Errorf("failed to write method expression in call: %w", err)
402
- }
446
+ // If expFun is a function literal, it needs to be wrapped in parentheses for IIFE syntax
447
+ if _, isFuncLit := expFun.(*ast.FuncLit); isFuncLit {
448
+ c.tsw.WriteLiterally("(")
449
+ if err := c.WriteValueExpr(expFun); err != nil {
450
+ return fmt.Errorf("failed to write function literal in call: %w", err)
451
+ }
452
+ c.tsw.WriteLiterally(")")
453
+ } else {
454
+ // Not an identifier (e.g., method call on a value)
455
+ if err := c.WriteValueExpr(expFun); err != nil {
456
+ return fmt.Errorf("failed to write method expression in call: %w", err)
457
+ }
403
458
 
404
- if funType := c.pkg.TypesInfo.TypeOf(expFun); funType != nil {
405
- if _, ok := funType.Underlying().(*types.Signature); ok {
406
- if _, isNamed := funType.(*types.Named); isNamed {
407
- c.tsw.WriteLiterally("!")
459
+ if funType := c.pkg.TypesInfo.TypeOf(expFun); funType != nil {
460
+ if _, ok := funType.Underlying().(*types.Signature); ok {
461
+ if _, isNamed := funType.(*types.Named); isNamed {
462
+ c.tsw.WriteLiterally("!")
463
+ }
408
464
  }
409
465
  }
410
466
  }
@@ -13,16 +13,16 @@ import (
13
13
  // access on an object or struct.
14
14
  // - For package selectors, it writes `PackageName.IdentifierName`. The `IdentifierName`
15
15
  // is written using `WriteIdent` which handles potential `.value` access if the
16
- // package-level variable is boxed.
16
+ // package-level variable is varrefed.
17
17
  // - For field or method access on an object (`exp.X`), it first writes the base
18
- // expression (`exp.X`) using `WriteValueExpr` (which handles its own boxing).
18
+ // expression (`exp.X`) using `WriteValueExpr` (which handles its own varRefing).
19
19
  // Then, it writes a dot (`.`) followed by the selected identifier (`exp.Sel`)
20
- // using `WriteIdent`, which appends `.value` if the field itself is boxed
20
+ // using `WriteIdent`, which appends `.value` if the field itself is varrefed
21
21
  // (e.g., accessing a field of primitive type through a pointer to a struct
22
22
  // where the field's address might have been taken).
23
23
  //
24
24
  // This function aims to correctly navigate Go's automatic dereferencing and
25
- // TypeScript's explicit boxing model.
25
+ // TypeScript's explicit varRefing model.
26
26
  func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
27
27
  // Check if this is a package selector (e.g., time.Now)
28
28
  if pkgIdent, isPkgIdent := exp.X.(*ast.Ident); isPkgIdent {
@@ -31,75 +31,120 @@ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
31
31
  // Package selectors should never use .value on the package name
32
32
  c.tsw.WriteLiterally(pkgIdent.Name)
33
33
  c.tsw.WriteLiterally(".")
34
- // Write the selected identifier, allowing .value if it's a boxed package variable
34
+ // Write the selected identifier, allowing .value if it's a varrefed package variable
35
35
  c.WriteIdent(exp.Sel, true)
36
36
  return nil
37
37
  }
38
38
  }
39
39
  }
40
40
 
41
- // --- Special case for dereferenced pointer to struct with field access: (*p).field ---
41
+ // --- Special case for dereferenced pointer to struct with field access: (*p).field or (**p).field etc ---
42
42
  var baseExpr ast.Expr = exp.X
43
43
  // Look inside parentheses if present
44
44
  if parenExpr, isParen := exp.X.(*ast.ParenExpr); isParen {
45
45
  baseExpr = parenExpr.X
46
46
  }
47
47
 
48
+ // Check if we have one or more star expressions (dereferences)
48
49
  if starExpr, isStarExpr := baseExpr.(*ast.StarExpr); isStarExpr {
49
- // Get the type of the pointer being dereferenced (e.g., type of 'p' in *p)
50
- ptrType := c.pkg.TypesInfo.TypeOf(starExpr.X)
51
- if ptrType != nil {
52
- if ptrTypeUnwrapped, ok := ptrType.(*types.Pointer); ok {
53
- elemType := ptrTypeUnwrapped.Elem()
54
- if elemType != nil {
55
- // If it's a pointer to a struct, handle field access specially
56
- if _, isStruct := elemType.Underlying().(*types.Struct); isStruct {
57
- // Get the object for the pointer variable itself (e.g., 'p')
58
- var ptrObj types.Object
59
- if ptrIdent, isIdent := starExpr.X.(*ast.Ident); isIdent {
60
- ptrObj = c.pkg.TypesInfo.ObjectOf(ptrIdent)
61
- }
50
+ // Count the levels of dereference and find the innermost expression
51
+ dereferenceCount := 0
52
+ currentExpr := baseExpr
53
+ for {
54
+ if star, ok := currentExpr.(*ast.StarExpr); ok {
55
+ dereferenceCount++
56
+ currentExpr = star.X
57
+ } else {
58
+ break
59
+ }
60
+ }
62
61
 
63
- // Write the pointer expression (e.g., p or p.value if p is boxed)
64
- if err := c.WriteValueExpr(starExpr.X); err != nil {
65
- return fmt.Errorf("failed to write pointer expression for (*p).field: %w", err)
66
- }
62
+ // Get the type of the innermost expression (the pointer variable)
63
+ innerType := c.pkg.TypesInfo.TypeOf(currentExpr)
64
+ if innerType != nil {
65
+ // Check if after all dereferences we end up with a struct
66
+ finalType := innerType
67
+ for i := 0; i < dereferenceCount; i++ {
68
+ if ptrType, ok := finalType.(*types.Pointer); ok {
69
+ finalType = ptrType.Elem()
70
+ } else {
71
+ break
72
+ }
73
+ }
67
74
 
68
- // Add ! for non-null assertion
69
- c.tsw.WriteLiterally("!")
75
+ // If the final type is a struct, handle field access specially
76
+ if _, isStruct := finalType.Underlying().(*types.Struct); isStruct {
77
+ // Write the fully dereferenced expression
78
+ if err := c.WriteValueExpr(starExpr); err != nil {
79
+ return fmt.Errorf("failed to write dereferenced expression for field access: %w", err)
80
+ }
70
81
 
71
- // Add .value ONLY if the pointer variable itself needs boxed access
72
- // This handles the case where 'p' points to a boxed struct (e.g., p = s where s is Box<MyStruct>)
73
- if ptrObj != nil && c.analysis.NeedsBoxedAccess(ptrObj) {
74
- c.tsw.WriteLiterally(".value")
82
+ // Check if we need an extra .value for varrefed struct access
83
+ // This happens when the struct being pointed to is varrefed
84
+ needsExtraValue := false
85
+ if ident, ok := currentExpr.(*ast.Ident); ok {
86
+ if obj := c.pkg.TypesInfo.ObjectOf(ident); obj != nil {
87
+ // Check if after dereferencing, we get a varrefed struct
88
+ ptrType := obj.Type()
89
+ for i := 0; i < dereferenceCount; i++ {
90
+ if ptr, ok := ptrType.(*types.Pointer); ok {
91
+ ptrType = ptr.Elem()
92
+ }
93
+ }
94
+ // If the final pointed-to type suggests the struct is varrefed
95
+ // (i.e., the dereference operation results in VarRef<Struct>)
96
+ if c.analysis.NeedsVarRefAccess(obj) {
97
+ needsExtraValue = true
75
98
  }
76
-
77
- // Add .field
78
- c.tsw.WriteLiterally(".")
79
- c.WriteIdent(exp.Sel, false) // Don't add .value to the field itself
80
- return nil
81
99
  }
82
100
  }
101
+
102
+ if needsExtraValue {
103
+ c.tsw.WriteLiterally("!.value")
104
+ }
105
+
106
+ // Add .field
107
+ c.tsw.WriteLiterally(".")
108
+ c.WriteIdent(exp.Sel, false) // Don't add .value to the field itself
109
+ return nil
83
110
  }
84
111
  }
85
112
  }
86
113
  // --- End Special Case ---
87
114
 
88
115
  // Fallback / Normal Case (e.g., obj.Field, pkg.Var, method calls)
89
- // WriteValueExpr handles adding .value for the base variable itself if it's boxed.
116
+ // WriteValueExpr handles adding .value for the base variable itself if it's varrefed.
90
117
  if err := c.WriteValueExpr(exp.X); err != nil {
91
118
  return fmt.Errorf("failed to write selector base expression: %w", err)
92
119
  }
93
120
 
94
- // Add .
95
- c.tsw.WriteLiterally(".")
121
+ // Add null assertion for selector expressions when accessing fields/methods on nullable types
122
+ // In Go, accessing fields or calling methods on nil pointers/interfaces panics, so we should throw in TypeScript
123
+ baseType := c.pkg.TypesInfo.TypeOf(exp.X)
124
+ if baseType != nil {
125
+ // Check if the base is a pointer type
126
+ if _, isPtr := baseType.(*types.Pointer); isPtr {
127
+ c.tsw.WriteLiterally("!.")
128
+ } else if _, isInterface := baseType.Underlying().(*types.Interface); isInterface {
129
+ // For interface types, add null assertion since interfaces can be nil
130
+ c.tsw.WriteLiterally("!.")
131
+ } else if callExpr, isCall := exp.X.(*ast.CallExpr); isCall {
132
+ // For function calls that return nullable types, add null assertion
133
+ _ = callExpr // Use the variable to avoid unused error
134
+ c.tsw.WriteLiterally("!.")
135
+ } else {
136
+ // Add .
137
+ c.tsw.WriteLiterally(".")
138
+ }
139
+ } else {
140
+ // Add .
141
+ c.tsw.WriteLiterally(".")
142
+ }
96
143
 
97
144
  // Write the field/method name.
98
- // Pass 'true' to WriteIdent to potentially add '.value' if the field itself
99
- // needs boxed access (e.g., accessing a primitive field via pointer where
100
- // the field's address might have been taken elsewhere - less common but possible).
101
- // For simple struct field access like p.Val or (*p).Val, WriteIdent(..., true)
102
- // relies on NeedsBoxedAccess for the field 'Val', which should typically be false.
103
- c.WriteIdent(exp.Sel, true)
145
+ // Pass 'false' to WriteIdent to NOT add '.value' for struct fields.
146
+ // Struct fields use getters/setters, so we don't want to add .value here.
147
+ // The setter will handle the internal .value access.
148
+ c.WriteIdent(exp.Sel, false)
104
149
  return nil
105
150
  }