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
@@ -13,27 +13,28 @@ import (
13
13
  //
14
14
  // It handles several types of composite literals:
15
15
  // - Map literals (e.g., `map[K]V{k1: v1}`): Translated to `new Map([[k1_ts, v1_ts]])`.
16
- // Values are processed by `writeBoxedValue`.
16
+ // Values are processed by `WriteVarRefedValue`.
17
17
  // - Array/Slice literals (e.g., `[]T{e1, e2}`, `[N]T{idx: val}`):
18
18
  // - For `[]byte{...}`, translated to `new Uint8Array([...])`.
19
19
  // - For other `[]T` or `[N]T`, translated using the `$.arrayToSlice<T_ts>([...])` runtime helper.
20
20
  // It handles both keyed and unkeyed elements, infers length if necessary,
21
21
  // and uses zero values for uninitialized array elements.
22
22
  // Multi-dimensional arrays/slices pass a depth parameter to `$.arrayToSlice`.
23
- // Element values are processed by `writeBoxedValue`.
23
+ // Element values are processed by `WriteVarRefedValue`.
24
24
  // - Struct literals:
25
25
  // - Named structs (e.g., `MyStruct{F: v}` or `&MyStruct{F: v}`): Translated to
26
26
  // `new MyStruct_ts({ F: v_ts, ... })`. The constructor typically uses an `_init` method.
27
27
  // - Anonymous structs (e.g., `struct{F int}{F: v}`): Translated to TypeScript
28
28
  // object literals `{ F: v_ts, ... }`.
29
29
  // It processes keyed elements (`FieldName: Value`) and unkeyed elements (for anonymous
30
- // structs or arrays). Field values are processed by `writeBoxedValue`.
30
+ // structs or arrays). Field values are processed by `WriteVarRefedValue`.
31
31
  // Embedded struct fields are initialized, and explicit initializers for embedded
32
32
  // structs (e.g. `Outer{InnerField: InnerType{...}}`) are handled.
33
- // The function uses `c.analysis` to determine correct value access (e.g., `.value` for boxed fields).
33
+ // The function uses `c.analysis` to determine correct value access (e.g., `.value` for var-refed fields).
34
34
  func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
35
35
  // Get the type of the composite literal
36
36
  litType := c.pkg.TypesInfo.TypeOf(exp)
37
+
37
38
  if exp.Type != nil {
38
39
  // Handle map literals: map[K]V{k1: v1, k2: v2}
39
40
  if _, isMapType := exp.Type.(*ast.MapType); isMapType {
@@ -47,11 +48,11 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
47
48
 
48
49
  if kv, ok := elm.(*ast.KeyValueExpr); ok {
49
50
  c.tsw.WriteLiterally("[")
50
- if err := c.WriteBoxedValue(kv.Key); err != nil {
51
+ if err := c.WriteVarRefedValue(kv.Key); err != nil {
51
52
  return fmt.Errorf("failed to write map literal key: %w", err)
52
53
  }
53
54
  c.tsw.WriteLiterally(", ")
54
- if err := c.WriteBoxedValue(kv.Value); err != nil {
55
+ if err := c.WriteVarRefedValue(kv.Value); err != nil {
55
56
  return fmt.Errorf("failed to write map literal value: %w", err)
56
57
  }
57
58
  c.tsw.WriteLiterally("]")
@@ -139,7 +140,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
139
140
  hasKeyedElements = true
140
141
  } else {
141
142
  c.tsw.WriteCommentInline("unhandled keyed array literal key type")
142
- if err := c.WriteBoxedValue(elm); err != nil {
143
+ if err := c.WriteVarRefedValue(elm); err != nil {
143
144
  return fmt.Errorf("failed to write keyed array literal element with unhandled key type: %w", err)
144
145
  }
145
146
  }
@@ -167,7 +168,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
167
168
  c.tsw.WriteLiterally(", ")
168
169
  }
169
170
  if elm, ok := elements[i]; ok && elm != nil {
170
- if err := c.WriteBoxedValue(elm); err != nil {
171
+ if err := c.WriteVarRefedValue(elm); err != nil {
171
172
  return fmt.Errorf("failed to write array literal element: %w", err)
172
173
  }
173
174
  } else {
@@ -340,7 +341,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
340
341
  }
341
342
  c.tsw.WriteLiterally(keyName)
342
343
  c.tsw.WriteLiterally(": ")
343
- if err := c.WriteBoxedValue(directFields[keyName]); err != nil {
344
+ if err := c.WriteVarRefedValue(directFields[keyName]); err != nil {
344
345
  return err
345
346
  }
346
347
  firstFieldWritten = true
@@ -368,14 +369,14 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
368
369
  if i > 0 {
369
370
  c.tsw.WriteLiterally(", ")
370
371
  }
371
- if err := c.WriteBoxedValue(elem); err != nil {
372
+ if err := c.WriteVarRefedValue(elem); err != nil {
372
373
  return err
373
374
  }
374
375
  }
375
376
  c.tsw.WriteLiterally("}")
376
377
  } else {
377
378
  // Not a composite literal, write it normally
378
- if err := c.WriteBoxedValue(explicitEmbedded[embeddedName]); err != nil {
379
+ if err := c.WriteVarRefedValue(explicitEmbedded[embeddedName]); err != nil {
379
380
  return err
380
381
  }
381
382
  }
@@ -414,7 +415,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
414
415
  }
415
416
  c.tsw.WriteLiterally(keyName) // Field name within the embedded struct
416
417
  c.tsw.WriteLiterally(": ")
417
- if err := c.WriteBoxedValue(fieldsMap[keyName]); err != nil {
418
+ if err := c.WriteVarRefedValue(fieldsMap[keyName]); err != nil {
418
419
  return err
419
420
  }
420
421
  }
@@ -436,7 +437,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
436
437
  if i != 0 {
437
438
  c.tsw.WriteLiterally(", ")
438
439
  }
439
- if err := c.WriteBoxedValue(elm); err != nil {
440
+ if err := c.WriteVarRefedValue(elm); err != nil {
440
441
  return fmt.Errorf("failed to write literal field: %w", err)
441
442
  }
442
443
  }
@@ -448,60 +449,124 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
448
449
 
449
450
  // Untyped composite literal. Let's use type information to determine what it is.
450
451
  // First try to get the type information for the expression
451
- isObject := false
452
452
  if tv, ok := c.pkg.TypesInfo.Types[exp]; ok && tv.Type != nil {
453
453
  underlying := tv.Type.Underlying()
454
454
  switch underlying.(type) {
455
455
  case *types.Map, *types.Struct:
456
- isObject = true
456
+ // Handle struct directly with the struct literal logic
457
+ if structType, ok := underlying.(*types.Struct); ok {
458
+ return c.writeUntypedStructLiteral(exp, structType) // true = anonymous
459
+ }
460
+ // Map case would be handled here
461
+ return fmt.Errorf("untyped map composite literals not yet supported")
457
462
  case *types.Array, *types.Slice:
458
- isObject = false
463
+ // Handle array/slice
464
+ return c.writeUntypedArrayLiteral(exp)
465
+ case *types.Pointer:
466
+ // Handle pointer to composite literal
467
+ ptrType := underlying.(*types.Pointer)
468
+ elemType := ptrType.Elem().Underlying()
469
+ switch elemType.(type) {
470
+ case *types.Struct:
471
+ // This is an anonymous struct literal with inferred pointer type
472
+ // Just create the struct object directly - no var-refing needed
473
+ // Anonymous literals are not variables, so they don't get var-refed
474
+ structType := elemType.(*types.Struct)
475
+ return c.writeUntypedStructLiteral(exp, structType) // true = anonymous
476
+ default:
477
+ return fmt.Errorf("unhandled pointer composite literal element type: %T", elemType)
478
+ }
459
479
  default:
460
480
  return fmt.Errorf("unhandled composite literal type: %T", underlying)
461
481
  }
462
482
  } else {
463
483
  return fmt.Errorf("could not determine composite literal type from type information")
464
484
  }
485
+ }
465
486
 
466
- if isObject {
467
- c.tsw.WriteLiterally("{ ")
468
- } else {
469
- c.tsw.WriteLiterally("[ ")
470
- }
471
-
487
+ // writeUntypedArrayLiteral handles untyped composite literals that are arrays/slices
488
+ func (c *GoToTSCompiler) writeUntypedArrayLiteral(exp *ast.CompositeLit) error {
489
+ c.tsw.WriteLiterally("[ ")
472
490
  for i, elm := range exp.Elts {
473
491
  if i != 0 {
474
492
  c.tsw.WriteLiterally(", ")
475
493
  }
476
- if err := c.WriteBoxedValue(elm); err != nil {
477
- return fmt.Errorf("failed to write untyped composite literal element: %w", err)
494
+ if err := c.WriteVarRefedValue(elm); err != nil {
495
+ return fmt.Errorf("failed to write untyped array literal element: %w", err)
478
496
  }
479
497
  }
480
- if isObject {
481
- c.tsw.WriteLiterally(" }")
482
- } else {
483
- c.tsw.WriteLiterally(" ]")
498
+ c.tsw.WriteLiterally(" ]")
499
+ return nil
500
+ }
501
+
502
+ // writeUntypedStructLiteral handles untyped composite literals that are structs or pointers to structs
503
+ func (c *GoToTSCompiler) writeUntypedStructLiteral(exp *ast.CompositeLit, structType *types.Struct) error {
504
+ // Create field mapping like the typed struct case
505
+ directFields := make(map[string]ast.Expr)
506
+
507
+ // Handle elements that are key-value pairs
508
+ for _, elt := range exp.Elts {
509
+ if kv, ok := elt.(*ast.KeyValueExpr); ok {
510
+ if keyIdent, ok := kv.Key.(*ast.Ident); ok {
511
+ directFields[keyIdent.Name] = kv.Value
512
+ }
513
+ }
484
514
  }
515
+
516
+ // Handle elements that are positional (no key specified)
517
+ if len(directFields) == 0 {
518
+ // If no key-value pairs, try to match positional values to struct fields
519
+ for i, elt := range exp.Elts {
520
+ if _, isKV := elt.(*ast.KeyValueExpr); !isKV && i < structType.NumFields() {
521
+ field := structType.Field(i)
522
+ directFields[field.Name()] = elt
523
+ }
524
+ }
525
+ }
526
+
527
+ // Write the object literal (always anonymous for untyped)
528
+ c.tsw.WriteLiterally("{")
529
+
530
+ firstFieldWritten := false
531
+ // Write fields in order
532
+ directKeys := make([]string, 0, len(directFields))
533
+ for k := range directFields {
534
+ directKeys = append(directKeys, k)
535
+ }
536
+ slices.Sort(directKeys)
537
+ for _, keyName := range directKeys {
538
+ if firstFieldWritten {
539
+ c.tsw.WriteLiterally(", ")
540
+ }
541
+ c.tsw.WriteLiterally(keyName)
542
+ c.tsw.WriteLiterally(": ")
543
+ if err := c.WriteVarRefedValue(directFields[keyName]); err != nil {
544
+ return err
545
+ }
546
+ firstFieldWritten = true
547
+ }
548
+
549
+ c.tsw.WriteLiterally("}")
485
550
  return nil
486
551
  }
487
552
 
488
- // WriteBoxedValue translates a Go expression (`ast.Expr`) into its TypeScript equivalent,
553
+ // WriteVarRefedValue translates a Go expression (`ast.Expr`) into its TypeScript equivalent,
489
554
  // specifically for use as a value within a composite literal (e.g., struct fields,
490
555
  // map keys/values, or array/slice elements). Its primary goal is to ensure that the
491
- // actual, unboxed value of the expression is used.
556
+ // actual un-refed value of the expression is used.
492
557
  //
493
558
  // How it works:
494
559
  // - Identifiers (`*ast.Ident`): Delegates to `c.WriteIdent(ident, true)`, forcing
495
560
  // the `accessValue` flag to `true`. This ensures that if `ident` refers to a
496
- // GoScript boxed variable, the generated TypeScript accesses its underlying `.value`
561
+ // GoScript var-refed variable, the generated TypeScript accesses its underlying `.value`
497
562
  // (e.g., `myVar.value`).
498
563
  // - Selector Expressions (`*ast.SelectorExpr`, e.g., `obj.Field`): Delegates to
499
564
  // `c.WriteSelectorExpr(e)`. This function handles the necessary logic for
500
- // accessing fields or methods, including any required unboxing if the field
501
- // itself or the object it's accessed on is boxed (e.g., `obj.value.field` or
565
+ // accessing fields or methods, including any required un-var-refing if the field
566
+ // itself or the object it's accessed on is var-refed (e.g., `obj.value.field` or
502
567
  // `obj.field.value`).
503
568
  // - Star Expressions (`*ast.StarExpr`, e.g., `*ptr`): Delegates to `c.WriteStarExpr(e)`.
504
- // This function handles pointer dereferencing, which in GoScript's boxing model
569
+ // This function handles pointer dereferencing, which in GoScript's var-refing model
505
570
  // often translates to accessing the `.value` field of the pointer (e.g., `ptr.value`).
506
571
  // - Basic Literals (`*ast.BasicLit`, e.g., `123`, `"hello"`): Delegates to
507
572
  // `c.WriteBasicLit(e)` for direct translation.
@@ -511,25 +576,25 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
511
576
  //
512
577
  // Necessity and Distinction from `WriteValueExpr`:
513
578
  // While `WriteValueExpr` is a general-purpose function for translating Go expressions
514
- // and also unboxes identifiers (by calling `WriteIdent` with `accessValue: true`),
515
- // `WriteBoxedValue` serves a specific and crucial role when called from `WriteCompositeLit`:
579
+ // and also un-var-refes identifiers (by calling `WriteIdent` with `accessValue: true`),
580
+ // `WriteVarRefedValue` serves a specific and crucial role when called from `WriteCompositeLit`:
516
581
  // 1. Clarity of Intent: It explicitly signals that for the constituents of a composite
517
- // literal, the *unboxed value* is mandatory.
582
+ // literal, the *un-var-refed value* is mandatory.
518
583
  // 2. Contract for `WriteCompositeLit`: It ensures that `WriteCompositeLit` receives
519
584
  // the correct values for initialization, insulating it from potential changes in
520
- // the default behavior of `WriteValueExpr` regarding unboxing.
585
+ // the default behavior of `WriteValueExpr` regarding un-var-refing.
521
586
  // 3. Prevents Recursion: `WriteValueExpr` handles `*ast.CompositeLit` nodes by
522
587
  // calling `WriteCompositeLit`. If `WriteCompositeLit` were to directly call
523
588
  // `WriteValueExpr` for its elements, it could lead to unintended recursion or
524
- // behavior if an element itself was another composite literal. `WriteBoxedValue`
589
+ // behavior if an element itself was another composite literal. `WriteVarRefedValue`
525
590
  // acts as a specific intermediary for the *elements*.
526
591
  //
527
- // In summary, `WriteBoxedValue` is a specialized dispatcher used by `WriteCompositeLit`
592
+ // In summary, `WriteVarRefedValue` is a specialized dispatcher used by `WriteCompositeLit`
528
593
  // to guarantee that all parts of a Go composite literal are initialized with their
529
- // proper, unboxed TypeScript values.
530
- func (c *GoToTSCompiler) WriteBoxedValue(expr ast.Expr) error {
594
+ // proper, unrefed TypeScript values.
595
+ func (c *GoToTSCompiler) WriteVarRefedValue(expr ast.Expr) error {
531
596
  if expr == nil {
532
- return fmt.Errorf("nil expression passed to writeBoxedValue")
597
+ return fmt.Errorf("nil expression passed to write var refed value")
533
598
  }
534
599
 
535
600
  // Handle different expression types
@@ -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
- }
@@ -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,62 +31,89 @@ 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
  }
@@ -115,11 +142,9 @@ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
115
142
  }
116
143
 
117
144
  // Write the field/method name.
118
- // Pass 'true' to WriteIdent to potentially add '.value' if the field itself
119
- // needs boxed access (e.g., accessing a primitive field via pointer where
120
- // the field's address might have been taken elsewhere - less common but possible).
121
- // For simple struct field access like p.Val or (*p).Val, WriteIdent(..., true)
122
- // relies on NeedsBoxedAccess for the field 'Val', which should typically be false.
123
- 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)
124
149
  return nil
125
150
  }