goscript 0.0.21 → 0.0.22

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.
@@ -0,0 +1,105 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/types"
7
+ )
8
+
9
+ // WriteSelectorExpr translates a Go selector expression (`ast.SelectorExpr`)
10
+ // used as a value (e.g., `obj.Field`, `pkg.Variable`, `structVar.Method()`)
11
+ // into its TypeScript equivalent.
12
+ // It distinguishes between package selectors (e.g., `time.Now`) and field/method
13
+ // access on an object or struct.
14
+ // - For package selectors, it writes `PackageName.IdentifierName`. The `IdentifierName`
15
+ // is written using `WriteIdent` which handles potential `.value` access if the
16
+ // package-level variable is boxed.
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).
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
21
+ // (e.g., accessing a field of primitive type through a pointer to a struct
22
+ // where the field's address might have been taken).
23
+ //
24
+ // This function aims to correctly navigate Go's automatic dereferencing and
25
+ // TypeScript's explicit boxing model.
26
+ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
27
+ // Check if this is a package selector (e.g., time.Now)
28
+ if pkgIdent, isPkgIdent := exp.X.(*ast.Ident); isPkgIdent {
29
+ if obj := c.pkg.TypesInfo.ObjectOf(pkgIdent); obj != nil {
30
+ if _, isPkg := obj.(*types.PkgName); isPkg {
31
+ // Package selectors should never use .value on the package name
32
+ c.tsw.WriteLiterally(pkgIdent.Name)
33
+ c.tsw.WriteLiterally(".")
34
+ // Write the selected identifier, allowing .value if it's a boxed package variable
35
+ c.WriteIdent(exp.Sel, true)
36
+ return nil
37
+ }
38
+ }
39
+ }
40
+
41
+ // --- Special case for dereferenced pointer to struct with field access: (*p).field ---
42
+ var baseExpr ast.Expr = exp.X
43
+ // Look inside parentheses if present
44
+ if parenExpr, isParen := exp.X.(*ast.ParenExpr); isParen {
45
+ baseExpr = parenExpr.X
46
+ }
47
+
48
+ 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
+ }
62
+
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
+ }
67
+
68
+ // Add ! for non-null assertion
69
+ c.tsw.WriteLiterally("!")
70
+
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")
75
+ }
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
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ // --- End Special Case ---
87
+
88
+ // 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.
90
+ if err := c.WriteValueExpr(exp.X); err != nil {
91
+ return fmt.Errorf("failed to write selector base expression: %w", err)
92
+ }
93
+
94
+ // Add .
95
+ c.tsw.WriteLiterally(".")
96
+
97
+ // 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)
104
+ return nil
105
+ }
@@ -0,0 +1,90 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ )
7
+
8
+ // WriteStarExpr translates a Go pointer dereference expression (`ast.StarExpr`, e.g., `*p`)
9
+ // into its TypeScript equivalent. This involves careful handling of Go's pointers
10
+ // and TypeScript's boxing mechanism for emulating pointer semantics.
11
+ //
12
+ // The translation depends on whether the pointer variable `p` itself is boxed and
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!`.
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).
23
+ //
24
+ // `WriteValueExpr(operand)` handles the initial unboxing of `p` if `p` itself is a boxed variable.
25
+ // 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`).
29
+ 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)
64
+ }
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
+ }
71
+
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)
77
+ }
78
+
79
+ // Add ! for null assertion - all pointers can be null in TypeScript
80
+ c.tsw.WriteLiterally("!")
81
+
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")
87
+ }
88
+
89
+ return nil
90
+ }
@@ -0,0 +1,182 @@
1
+ package compiler
2
+
3
+ import (
4
+ "go/ast"
5
+ "go/types"
6
+ )
7
+
8
+ // WriteTypeExpr translates a Go abstract syntax tree (AST) expression (`ast.Expr`)
9
+ // that represents a type into its TypeScript type equivalent using type information.
10
+ //
11
+ // It handles various Go type expressions:
12
+ // - Basic types (e.g., int, string, bool) -> TypeScript primitives (number, string, boolean)
13
+ // - Named types -> TypeScript class/interface names
14
+ // - Pointer types (`*T`) -> `$.Box<T_ts> | null`
15
+ // - Slice types (`[]T`) -> `$.Slice<T_ts>`
16
+ // - Array types (`[N]T`) -> `T_ts[]`
17
+ // - Map types (`map[K]V`) -> `Map<K_ts, V_ts>`
18
+ // - Channel types (`chan T`) -> `$.Channel<T_ts>`
19
+ // - Struct types -> TypeScript object types or class names
20
+ // - Interface types -> TypeScript interface types or "any"
21
+ // - Function types -> TypeScript function signatures
22
+ func (c *GoToTSCompiler) WriteTypeExpr(a ast.Expr) {
23
+ // Get type information for the expression and use WriteGoType
24
+ typ := c.pkg.TypesInfo.TypeOf(a)
25
+ c.WriteGoType(typ)
26
+ }
27
+
28
+ // writeTypeDescription writes the TypeInfo for a type expr.
29
+ func (c *GoToTSCompiler) writeTypeDescription(typeExpr ast.Expr) {
30
+ switch t := typeExpr.(type) {
31
+ case *ast.Ident:
32
+ if isPrimitiveType(t.Name) {
33
+ if tsType, ok := GoBuiltinToTypescript(t.Name); ok {
34
+ c.tsw.WriteLiterally("{")
35
+ c.tsw.WriteLiterally("kind: $.TypeKind.Basic, ")
36
+ c.tsw.WriteLiterallyf("name: '%s'", tsType)
37
+ c.tsw.WriteLiterally("}")
38
+ } else {
39
+ // Fallback for other primitive types
40
+ c.tsw.WriteLiterally("{")
41
+ c.tsw.WriteLiterally("kind: $.TypeKind.Basic, ")
42
+ c.tsw.WriteLiterallyf("name: '%s'", t.Name)
43
+ c.tsw.WriteLiterally("}")
44
+ }
45
+ } else {
46
+ // For named types, just use the name string
47
+ c.tsw.WriteLiterallyf("'%s'", t.Name)
48
+ }
49
+ case *ast.SelectorExpr:
50
+ if ident, ok := t.X.(*ast.Ident); ok {
51
+ c.tsw.WriteLiterallyf("'%s.%s'", ident.Name, t.Sel.Name)
52
+ }
53
+ case *ast.ArrayType:
54
+ typeKind := "$.TypeKind.Slice"
55
+ if t.Len != nil {
56
+ typeKind = "$.TypeKind.Array"
57
+ }
58
+
59
+ c.tsw.WriteLiterally("{")
60
+ c.tsw.WriteLiterallyf("kind: %s, ", typeKind)
61
+ c.tsw.WriteLiterally("elemType: ")
62
+ c.writeTypeDescription(t.Elt)
63
+ c.tsw.WriteLiterally("}")
64
+ case *ast.StructType:
65
+ c.tsw.WriteLiterally("{")
66
+ c.tsw.WriteLiterally("kind: $.TypeKind.Struct, ")
67
+
68
+ // Add field names and types to the struct type info
69
+ if t.Fields != nil && t.Fields.List != nil {
70
+ c.tsw.WriteLiterally("fields: {")
71
+
72
+ hasFields := false
73
+ for _, field := range t.Fields.List {
74
+ if len(field.Names) > 0 {
75
+ for _, name := range field.Names {
76
+ if hasFields {
77
+ c.tsw.WriteLiterally(", ")
78
+ }
79
+ c.tsw.WriteLiterallyf("'%s': ", name.Name)
80
+ c.writeTypeDescription(field.Type)
81
+ hasFields = true
82
+ }
83
+ }
84
+ }
85
+
86
+ c.tsw.WriteLiterally("}, ")
87
+ } else {
88
+ c.tsw.WriteLiterally("fields: {}, ")
89
+ }
90
+
91
+ c.tsw.WriteLiterally("methods: []")
92
+
93
+ c.tsw.WriteLiterally("}")
94
+ case *ast.MapType:
95
+ c.tsw.WriteLiterally("{")
96
+ c.tsw.WriteLiterally("kind: $.TypeKind.Map, ")
97
+ c.tsw.WriteLiterally("keyType: ")
98
+ c.writeTypeDescription(t.Key)
99
+ c.tsw.WriteLiterally(", ")
100
+ c.tsw.WriteLiterally("elemType: ")
101
+ c.writeTypeDescription(t.Value)
102
+ c.tsw.WriteLiterally("}")
103
+ case *ast.StarExpr:
104
+ c.tsw.WriteLiterally("{")
105
+ c.tsw.WriteLiterally("kind: $.TypeKind.Pointer, ")
106
+ c.tsw.WriteLiterally("elemType: ")
107
+ c.writeTypeDescription(t.X)
108
+ c.tsw.WriteLiterally("}")
109
+ case *ast.FuncType:
110
+ // For function types, create a type descriptor object with params and results
111
+ c.tsw.WriteLiterally("{")
112
+ c.tsw.WriteLiterally("kind: $.TypeKind.Function")
113
+
114
+ // Add name if this is a named function type
115
+ if namedType := c.pkg.TypesInfo.TypeOf(typeExpr); namedType != nil {
116
+ if named, ok := namedType.(*types.Named); ok {
117
+ c.tsw.WriteLiterallyf(", name: '%s'", named.Obj().Name())
118
+ }
119
+ }
120
+
121
+ // Add params if present
122
+ if t.Params != nil && len(t.Params.List) > 0 {
123
+ c.tsw.WriteLiterally(", params: [")
124
+ for i, param := range t.Params.List {
125
+ if i > 0 {
126
+ c.tsw.WriteLiterally(", ")
127
+ }
128
+ c.writeTypeDescription(param.Type)
129
+ }
130
+ c.tsw.WriteLiterally("]")
131
+ }
132
+
133
+ // Add results if present
134
+ if t.Results != nil && len(t.Results.List) > 0 {
135
+ c.tsw.WriteLiterally(", results: [")
136
+ for i, result := range t.Results.List {
137
+ if i > 0 {
138
+ c.tsw.WriteLiterally(", ")
139
+ }
140
+ c.writeTypeDescription(result.Type)
141
+ }
142
+ c.tsw.WriteLiterally("]")
143
+ }
144
+
145
+ c.tsw.WriteLiterally("}")
146
+ return
147
+ case *ast.ChanType:
148
+ c.tsw.WriteLiterally("{")
149
+ c.tsw.WriteLiterally("kind: $.TypeKind.Channel, ")
150
+ c.tsw.WriteLiterally("elemType: ")
151
+
152
+ // Add element type
153
+ if ident, ok := t.Value.(*ast.Ident); ok && isPrimitiveType(ident.Name) {
154
+ if tsType, ok := GoBuiltinToTypescript(ident.Name); ok {
155
+ c.tsw.WriteLiterallyf("'%s'", tsType)
156
+ } else {
157
+ c.tsw.WriteLiterallyf("'%s'", ident.Name) // Fallback
158
+ }
159
+ } else {
160
+ c.writeTypeDescription(t.Value)
161
+ }
162
+
163
+ // Add direction
164
+ c.tsw.WriteLiterally(", direction: ")
165
+ switch t.Dir {
166
+ case ast.SEND:
167
+ c.tsw.WriteLiterally("'send'")
168
+ case ast.RECV:
169
+ c.tsw.WriteLiterally("'receive'")
170
+ case ast.SEND | ast.RECV: // bidirectional
171
+ c.tsw.WriteLiterally("'both'")
172
+ default:
173
+ // This should not happen, but just in case
174
+ c.tsw.WriteLiterally("'both'")
175
+ }
176
+
177
+ c.tsw.WriteLiterally("}")
178
+ default:
179
+ // For other types, use the string representation
180
+ c.tsw.WriteLiterallyf("'%s'", c.getTypeNameString(typeExpr))
181
+ }
182
+ }
@@ -0,0 +1,119 @@
1
+ package compiler
2
+
3
+ import "go/ast"
4
+
5
+ // WriteValueExpr translates a Go abstract syntax tree (AST) expression (`ast.Expr`)
6
+ // that represents a value into its TypeScript value equivalent.
7
+ // This is a central dispatch function for various expression types:
8
+ // - Identifiers (`ast.Ident`): Delegates to `WriteIdent`, potentially adding `.value` for boxed variables.
9
+ // - Selector expressions (`ast.SelectorExpr`, e.g., `obj.Field` or `pkg.Var`): Delegates to `WriteSelectorExpr`.
10
+ // - Pointer dereferences (`ast.StarExpr`, e.g., `*ptr`): Delegates to `WriteStarExpr`.
11
+ // - Function calls (`ast.CallExpr`): Delegates to `WriteCallExpr`.
12
+ // - Unary operations (`ast.UnaryExpr`, e.g., `!cond`, `&val`): Delegates to `WriteUnaryExpr`.
13
+ // - Binary operations (`ast.BinaryExpr`, e.g., `a + b`): Delegates to `WriteBinaryExpr`.
14
+ // - Basic literals (`ast.BasicLit`, e.g., `123`, `"hello"`): Delegates to `WriteBasicLit`.
15
+ // - Composite literals (`ast.CompositeLit`, e.g., `MyStruct{}`): Delegates to `WriteCompositeLit`.
16
+ // - Key-value expressions (`ast.KeyValueExpr`): Delegates to `WriteKeyValueExpr`.
17
+ // - Type assertions in expression context (`ast.TypeAssertExpr`, e.g., `val.(Type)`): Delegates to `WriteTypeAssertExpr`.
18
+ // - Index expressions (`ast.IndexExpr`):
19
+ // - For maps: `myMap[key]` becomes `myMap.get(key) ?? zeroValue`.
20
+ // - For arrays/slices: `myArray[idx]` becomes `myArray![idx]`.
21
+ //
22
+ // - Slice expressions (`ast.SliceExpr`, e.g., `s[low:high:max]`): Translates to `$.slice(s, low, high, max)`.
23
+ // - Parenthesized expressions (`ast.ParenExpr`): Translates `(X)` to `(X)`.
24
+ // - Function literals (`ast.FuncLit`): Delegates to `WriteFuncLitValue`.
25
+ // Unhandled value expressions result in a comment.
26
+ func (c *GoToTSCompiler) WriteValueExpr(a ast.Expr) error {
27
+ switch exp := a.(type) {
28
+ case *ast.Ident:
29
+ c.WriteIdent(exp, true) // adds .value accessor
30
+ return nil
31
+ case *ast.SelectorExpr:
32
+ return c.WriteSelectorExpr(exp)
33
+ case *ast.StarExpr:
34
+ return c.WriteStarExpr(exp)
35
+ case *ast.CallExpr:
36
+ return c.WriteCallExpr(exp)
37
+ case *ast.UnaryExpr:
38
+ return c.WriteUnaryExpr(exp)
39
+ case *ast.BinaryExpr:
40
+ return c.WriteBinaryExpr(exp)
41
+ case *ast.BasicLit:
42
+ c.WriteBasicLit(exp)
43
+ return nil
44
+ case *ast.CompositeLit:
45
+ return c.WriteCompositeLit(exp)
46
+ case *ast.KeyValueExpr:
47
+ return c.WriteKeyValueExpr(exp)
48
+ case *ast.TypeAssertExpr:
49
+ // Handle type assertion in an expression context
50
+ return c.WriteTypeAssertExpr(exp)
51
+ case *ast.IndexExpr:
52
+ return c.WriteIndexExpr(exp)
53
+ case *ast.SliceExpr:
54
+ // Translate Go slice expression to $.goSlice(x, low, high, max)
55
+ c.tsw.WriteLiterally("$.goSlice(")
56
+ if err := c.WriteValueExpr(exp.X); err != nil {
57
+ return err
58
+ }
59
+ // low argument
60
+ c.tsw.WriteLiterally(", ")
61
+ if exp.Low != nil {
62
+ if err := c.WriteValueExpr(exp.Low); err != nil {
63
+ return err
64
+ }
65
+ } else {
66
+ c.tsw.WriteLiterally("undefined")
67
+ }
68
+ // high argument
69
+ c.tsw.WriteLiterally(", ")
70
+ if exp.High != nil {
71
+ if err := c.WriteValueExpr(exp.High); err != nil {
72
+ return err
73
+ }
74
+ } else {
75
+ c.tsw.WriteLiterally("undefined")
76
+ }
77
+ // max argument (only for full slice expressions)
78
+ if exp.Slice3 {
79
+ c.tsw.WriteLiterally(", ")
80
+ if exp.Max != nil {
81
+ if err := c.WriteValueExpr(exp.Max); err != nil {
82
+ return err
83
+ }
84
+ } else {
85
+ c.tsw.WriteLiterally("undefined")
86
+ }
87
+ }
88
+ c.tsw.WriteLiterally(")")
89
+ return nil
90
+ case *ast.ParenExpr:
91
+ // Check if this is a nil pointer to struct type cast: (*struct{})(nil)
92
+ if starExpr, isStarExpr := exp.X.(*ast.StarExpr); isStarExpr {
93
+ if _, isStructType := starExpr.X.(*ast.StructType); isStructType {
94
+ c.tsw.WriteLiterally("null")
95
+ return nil
96
+ }
97
+ }
98
+
99
+ // Check if this is a type cast with nil: (SomeType)(nil)
100
+ if ident, isIdent := exp.X.(*ast.Ident); isIdent && ident.Name == "nil" {
101
+ c.tsw.WriteLiterally("null")
102
+ return nil
103
+ }
104
+
105
+ // Translate (X) to (X)
106
+ // If we haven't written anything in this statement yet, prepend ;
107
+ c.tsw.WriteLiterally("(")
108
+ if err := c.WriteValueExpr(exp.X); err != nil {
109
+ return err
110
+ }
111
+ c.tsw.WriteLiterally(")")
112
+ return nil
113
+ case *ast.FuncLit:
114
+ return c.WriteFuncLitValue(exp)
115
+ default:
116
+ c.tsw.WriteCommentLinef("unhandled value expr: %T", exp)
117
+ return nil
118
+ }
119
+ }