goscript 0.0.21 → 0.0.23
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.
- package/cmd/goscript/cmd_compile.go +2 -2
- package/compiler/analysis.go +229 -51
- package/compiler/assignment.go +412 -0
- package/compiler/compiler.go +185 -5885
- package/compiler/compiler_test.go +40 -8
- package/compiler/composite-lit.go +552 -0
- package/compiler/config.go +3 -0
- package/compiler/decl.go +259 -0
- package/compiler/expr-call.go +479 -0
- package/compiler/expr-selector.go +125 -0
- package/compiler/expr-star.go +90 -0
- package/compiler/expr-type.go +309 -0
- package/compiler/expr-value.go +89 -0
- package/compiler/expr.go +591 -0
- package/compiler/field.go +169 -0
- package/compiler/lit.go +131 -0
- package/compiler/primitive.go +148 -0
- package/compiler/{write-type-spec.go → spec-struct.go} +211 -204
- package/compiler/spec-value.go +226 -0
- package/compiler/spec.go +272 -0
- package/compiler/stmt-assign.go +439 -0
- package/compiler/stmt-for.go +178 -0
- package/compiler/stmt-range.go +235 -0
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +792 -0
- package/compiler/type-assert.go +209 -0
- package/compiler/type-info.go +141 -0
- package/compiler/type.go +618 -0
- package/go.mod +2 -1
- package/go.sum +4 -2
- package/package.json +6 -6
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2114
- package/dist/builtin/builtin.d.ts +0 -495
- package/dist/builtin/builtin.js +0 -1490
- package/dist/builtin/builtin.js.map +0 -1
- /package/compiler/{writer.go → code-writer.go} +0 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"go/ast"
|
|
6
|
+
"go/token"
|
|
7
|
+
"go/types"
|
|
8
|
+
|
|
9
|
+
"github.com/pkg/errors"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// WriteCallExpr translates a Go function call expression (`ast.CallExpr`)
|
|
13
|
+
// into its TypeScript equivalent.
|
|
14
|
+
// It handles several Go built-in functions specially:
|
|
15
|
+
// - `println(...)` becomes `console.log(...)`.
|
|
16
|
+
// - `panic(...)` becomes `$.panic(...)`.
|
|
17
|
+
// - `len(arg)` becomes `$.len(arg)`.
|
|
18
|
+
// - `cap(arg)` becomes `$.cap(arg)`.
|
|
19
|
+
// - `delete(m, k)` becomes `$.deleteMapEntry(m, k)`.
|
|
20
|
+
// - `make(chan T, size)` becomes `$.makeChannel<T_ts>(size, zeroValueForT)`.
|
|
21
|
+
// - `make(map[K]V)` becomes `$.makeMap<K_ts, V_ts>()`.
|
|
22
|
+
// - `make([]T, len, cap)` becomes `$.makeSlice<T_ts>(len, cap)`.
|
|
23
|
+
// - `make([]byte, len, cap)` becomes `new Uint8Array(len)`.
|
|
24
|
+
// - `string(runeVal)` becomes `String.fromCharCode(runeVal)`.
|
|
25
|
+
// - `string([]runeVal)` becomes `$.runesToString(sliceVal)`.
|
|
26
|
+
// - `string([]byteVal)` becomes `$.bytesToString(sliceVal)`.
|
|
27
|
+
// - `[]rune(stringVal)` becomes `$.stringToRunes(stringVal)“.
|
|
28
|
+
// - `[]byte(stringVal)` becomes `$.stringToBytes(stringVal)`.
|
|
29
|
+
// - `close(ch)` becomes `ch.close()`.
|
|
30
|
+
// - `append(slice, elems...)` becomes `$.append(slice, elems...)`.
|
|
31
|
+
// - `byte(val)` becomes `$.byte(val)`.
|
|
32
|
+
// For other function calls:
|
|
33
|
+
// - If the `Analysis` data indicates the function is asynchronous (e.g., due to
|
|
34
|
+
// channel operations or `go`/`defer` usage within it), the call is prefixed with `await`.
|
|
35
|
+
// - Otherwise, it's translated as a standard TypeScript function call: `funcName(arg1, arg2)`.
|
|
36
|
+
//
|
|
37
|
+
// Arguments are recursively translated using `WriteValueExpr`.
|
|
38
|
+
func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
39
|
+
expFun := exp.Fun
|
|
40
|
+
|
|
41
|
+
// Handle any type conversion with nil argument
|
|
42
|
+
if len(exp.Args) == 1 {
|
|
43
|
+
if nilIdent, isIdent := exp.Args[0].(*ast.Ident); isIdent && nilIdent.Name == "nil" {
|
|
44
|
+
// Handle nil pointer to struct type conversions: (*struct{})(nil)
|
|
45
|
+
if starExpr, isStarExpr := expFun.(*ast.StarExpr); isStarExpr {
|
|
46
|
+
if _, isStructType := starExpr.X.(*ast.StructType); isStructType {
|
|
47
|
+
c.tsw.WriteLiterally("null")
|
|
48
|
+
return nil
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
c.tsw.WriteLiterally("null")
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle array type conversions like []rune(string)
|
|
58
|
+
if arrayType, isArrayType := expFun.(*ast.ArrayType); isArrayType {
|
|
59
|
+
// Check if it's a []rune type
|
|
60
|
+
if ident, isIdent := arrayType.Elt.(*ast.Ident); isIdent && ident.Name == "rune" {
|
|
61
|
+
// Check if the argument is a string
|
|
62
|
+
if len(exp.Args) == 1 {
|
|
63
|
+
arg := exp.Args[0]
|
|
64
|
+
if tv, ok := c.pkg.TypesInfo.Types[arg]; ok && tv.Type != nil {
|
|
65
|
+
if basic, isBasic := tv.Type.Underlying().(*types.Basic); isBasic && basic.Kind() == types.String {
|
|
66
|
+
// Translate []rune(stringValue) to $.stringToRunes(stringValue)
|
|
67
|
+
c.tsw.WriteLiterally("$.stringToRunes(")
|
|
68
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
69
|
+
return fmt.Errorf("failed to write argument for []rune(string) conversion: %w", err)
|
|
70
|
+
}
|
|
71
|
+
c.tsw.WriteLiterally(")")
|
|
72
|
+
return nil // Handled []rune(string)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
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
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if funIdent, funIsIdent := expFun.(*ast.Ident); funIsIdent {
|
|
97
|
+
switch funIdent.String() {
|
|
98
|
+
case "panic":
|
|
99
|
+
c.tsw.WriteLiterally("$.panic")
|
|
100
|
+
case "println":
|
|
101
|
+
c.tsw.WriteLiterally("console.log")
|
|
102
|
+
case "len":
|
|
103
|
+
// Translate len(arg) to $.len(arg)
|
|
104
|
+
if len(exp.Args) != 1 {
|
|
105
|
+
return errors.Errorf("unhandled len call with incorrect number of arguments: %d != 1", len(exp.Args))
|
|
106
|
+
}
|
|
107
|
+
c.tsw.WriteLiterally("$.len")
|
|
108
|
+
case "cap":
|
|
109
|
+
// Translate cap(arg) to $.cap(arg)
|
|
110
|
+
if len(exp.Args) != 1 {
|
|
111
|
+
return errors.Errorf("unhandled cap call with incorrect number of arguments: %d != 1", len(exp.Args))
|
|
112
|
+
}
|
|
113
|
+
c.tsw.WriteLiterally("$.cap")
|
|
114
|
+
case "delete":
|
|
115
|
+
// Translate delete(map, key) to $.deleteMapEntry(map, key)
|
|
116
|
+
if len(exp.Args) != 2 {
|
|
117
|
+
return errors.Errorf("unhandled delete call with incorrect number of arguments: %d != 2", len(exp.Args))
|
|
118
|
+
}
|
|
119
|
+
c.tsw.WriteLiterally("$.deleteMapEntry")
|
|
120
|
+
case "make":
|
|
121
|
+
// First check if we have a channel type
|
|
122
|
+
if typ := c.pkg.TypesInfo.TypeOf(exp.Args[0]); typ != nil {
|
|
123
|
+
if chanType, ok := typ.Underlying().(*types.Chan); ok {
|
|
124
|
+
// Handle channel creation: make(chan T, bufferSize) or make(chan T)
|
|
125
|
+
c.tsw.WriteLiterally("$.makeChannel<")
|
|
126
|
+
c.WriteGoType(chanType.Elem(), GoTypeContextGeneral)
|
|
127
|
+
c.tsw.WriteLiterally(">(")
|
|
128
|
+
|
|
129
|
+
// If buffer size is provided, add it
|
|
130
|
+
if len(exp.Args) >= 2 {
|
|
131
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil {
|
|
132
|
+
return fmt.Errorf("failed to write buffer size in makeChannel: %w", err)
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
// Default to 0 (unbuffered channel)
|
|
136
|
+
c.tsw.WriteLiterally("0")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
c.tsw.WriteLiterally(", ") // Add comma for zero value argument
|
|
140
|
+
|
|
141
|
+
// Write the zero value for the channel's element type
|
|
142
|
+
if chanType.Elem().String() == "struct{}" {
|
|
143
|
+
c.tsw.WriteLiterally("{}")
|
|
144
|
+
} else {
|
|
145
|
+
c.WriteZeroValueForType(chanType.Elem())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Add direction parameter
|
|
149
|
+
c.tsw.WriteLiterally(", ")
|
|
150
|
+
|
|
151
|
+
// Determine channel direction
|
|
152
|
+
switch chanType.Dir() {
|
|
153
|
+
case types.SendRecv:
|
|
154
|
+
c.tsw.WriteLiterally("'both'")
|
|
155
|
+
case types.SendOnly:
|
|
156
|
+
c.tsw.WriteLiterally("'send'")
|
|
157
|
+
case types.RecvOnly:
|
|
158
|
+
c.tsw.WriteLiterally("'receive'")
|
|
159
|
+
default:
|
|
160
|
+
c.tsw.WriteLiterally("'both'") // Default to bidirectional
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
c.tsw.WriteLiterally(")")
|
|
164
|
+
return nil // Handled make for channel
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Handle make for slices: make([]T, len, cap) or make([]T, len)
|
|
168
|
+
if len(exp.Args) >= 1 {
|
|
169
|
+
// Handle map creation: make(map[K]V)
|
|
170
|
+
if mapType, ok := exp.Args[0].(*ast.MapType); ok {
|
|
171
|
+
c.tsw.WriteLiterally("$.makeMap<")
|
|
172
|
+
c.WriteTypeExpr(mapType.Key) // Write the key type
|
|
173
|
+
c.tsw.WriteLiterally(", ")
|
|
174
|
+
c.WriteTypeExpr(mapType.Value) // Write the value type
|
|
175
|
+
c.tsw.WriteLiterally(">()")
|
|
176
|
+
return nil // Handled make for map
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Handle slice creation
|
|
180
|
+
if _, ok := exp.Args[0].(*ast.ArrayType); ok {
|
|
181
|
+
// Get the slice type information
|
|
182
|
+
sliceType := c.pkg.TypesInfo.TypeOf(exp.Args[0])
|
|
183
|
+
if sliceType == nil {
|
|
184
|
+
return errors.New("could not get type information for slice in make call")
|
|
185
|
+
}
|
|
186
|
+
goUnderlyingType, ok := sliceType.Underlying().(*types.Slice)
|
|
187
|
+
if !ok {
|
|
188
|
+
return errors.New("expected slice type for make call")
|
|
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
|
+
}
|
|
207
|
+
|
|
208
|
+
c.tsw.WriteLiterally("$.makeSlice<")
|
|
209
|
+
c.WriteGoType(goElemType, GoTypeContextGeneral) // Write the element type
|
|
210
|
+
c.tsw.WriteLiterally(">(")
|
|
211
|
+
|
|
212
|
+
if len(exp.Args) >= 2 {
|
|
213
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Length
|
|
214
|
+
return err
|
|
215
|
+
}
|
|
216
|
+
if len(exp.Args) == 3 {
|
|
217
|
+
c.tsw.WriteLiterally(", ")
|
|
218
|
+
if err := c.WriteValueExpr(exp.Args[2]); err != nil { // Capacity
|
|
219
|
+
return err
|
|
220
|
+
}
|
|
221
|
+
} else if len(exp.Args) > 3 {
|
|
222
|
+
return errors.New("makeSlice expects 2 or 3 arguments")
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
// If no length is provided, default to 0
|
|
226
|
+
c.tsw.WriteLiterally("0")
|
|
227
|
+
}
|
|
228
|
+
c.tsw.WriteLiterally(")")
|
|
229
|
+
return nil // Handled make for slice
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Fallthrough for unhandled make calls (e.g., channels)
|
|
233
|
+
return errors.New("unhandled make call")
|
|
234
|
+
case "string":
|
|
235
|
+
// Handle string() conversion
|
|
236
|
+
if len(exp.Args) == 1 {
|
|
237
|
+
arg := exp.Args[0]
|
|
238
|
+
|
|
239
|
+
// Case 1: Argument is a string literal string("...")
|
|
240
|
+
if basicLit, isBasicLit := arg.(*ast.BasicLit); isBasicLit && basicLit.Kind == token.STRING {
|
|
241
|
+
// Translate string("...") to "..." (no-op)
|
|
242
|
+
c.WriteBasicLit(basicLit)
|
|
243
|
+
return nil // Handled string literal conversion
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Case 2: Argument is a rune (int32) or a call to rune()
|
|
247
|
+
innerCall, isCallExpr := arg.(*ast.CallExpr)
|
|
248
|
+
|
|
249
|
+
if isCallExpr {
|
|
250
|
+
// Check if it's a call to rune()
|
|
251
|
+
if innerFunIdent, innerFunIsIdent := innerCall.Fun.(*ast.Ident); innerFunIsIdent && innerFunIdent.String() == "rune" {
|
|
252
|
+
// Translate string(rune(val)) to String.fromCharCode(val)
|
|
253
|
+
if len(innerCall.Args) == 1 {
|
|
254
|
+
c.tsw.WriteLiterally("String.fromCharCode(")
|
|
255
|
+
if err := c.WriteValueExpr(innerCall.Args[0]); err != nil {
|
|
256
|
+
return fmt.Errorf("failed to write argument for string(rune) conversion: %w", err)
|
|
257
|
+
}
|
|
258
|
+
c.tsw.WriteLiterally(")")
|
|
259
|
+
return nil // Handled string(rune)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Handle direct string(int32) conversion
|
|
265
|
+
// This assumes 'rune' is int32
|
|
266
|
+
if tv, ok := c.pkg.TypesInfo.Types[arg]; ok {
|
|
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) {
|
|
277
|
+
// Translate string(rune_val) to String.fromCharCode(rune_val)
|
|
278
|
+
c.tsw.WriteLiterally("String.fromCharCode(")
|
|
279
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
280
|
+
return fmt.Errorf("failed to write argument for string(int32) conversion: %w", err)
|
|
281
|
+
}
|
|
282
|
+
c.tsw.WriteLiterally(")")
|
|
283
|
+
return nil // Handled string(int32)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Case 3: Argument is a slice of runes or bytes string([]rune{...}) or string([]byte{...})
|
|
287
|
+
if sliceType, isSlice := tv.Type.Underlying().(*types.Slice); isSlice {
|
|
288
|
+
if basic, isBasic := sliceType.Elem().Underlying().(*types.Basic); isBasic {
|
|
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(...)
|
|
301
|
+
c.tsw.WriteLiterally("$.runesToString(")
|
|
302
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
303
|
+
return fmt.Errorf("failed to write argument for string([]rune) conversion: %w", err)
|
|
304
|
+
}
|
|
305
|
+
c.tsw.WriteLiterally(")")
|
|
306
|
+
return nil // Handled string([]rune)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
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
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Return error for other unhandled string conversions
|
|
329
|
+
return fmt.Errorf("unhandled string conversion: %s", exp.Fun)
|
|
330
|
+
case "close":
|
|
331
|
+
// Translate close(ch) to ch.close()
|
|
332
|
+
if len(exp.Args) == 1 {
|
|
333
|
+
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
334
|
+
return fmt.Errorf("failed to write channel in close call: %w", err)
|
|
335
|
+
}
|
|
336
|
+
c.tsw.WriteLiterally(".close()")
|
|
337
|
+
return nil // Handled close
|
|
338
|
+
}
|
|
339
|
+
return errors.New("unhandled close call with incorrect number of arguments")
|
|
340
|
+
case "append":
|
|
341
|
+
// Translate append(slice, elements...) to $.append(slice, elements...)
|
|
342
|
+
if len(exp.Args) >= 1 {
|
|
343
|
+
c.tsw.WriteLiterally("$.append(")
|
|
344
|
+
// The first argument is the slice
|
|
345
|
+
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
346
|
+
return fmt.Errorf("failed to write slice in append call: %w", err)
|
|
347
|
+
}
|
|
348
|
+
// The remaining arguments are the elements to append
|
|
349
|
+
for i, arg := range exp.Args[1:] {
|
|
350
|
+
if i > 0 || len(exp.Args) > 1 { // Add comma before elements if there are any
|
|
351
|
+
c.tsw.WriteLiterally(", ")
|
|
352
|
+
}
|
|
353
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
354
|
+
return fmt.Errorf("failed to write argument %d in append call: %w", i+1, err)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
c.tsw.WriteLiterally(")")
|
|
358
|
+
return nil // Handled append
|
|
359
|
+
}
|
|
360
|
+
return errors.New("unhandled append call with incorrect number of arguments")
|
|
361
|
+
case "byte":
|
|
362
|
+
// Translate byte(val) to $.byte(val)
|
|
363
|
+
if len(exp.Args) == 1 {
|
|
364
|
+
c.tsw.WriteLiterally("$.byte(")
|
|
365
|
+
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
366
|
+
return err
|
|
367
|
+
}
|
|
368
|
+
c.tsw.WriteLiterally(")")
|
|
369
|
+
return nil // Handled byte
|
|
370
|
+
}
|
|
371
|
+
return errors.New("unhandled byte call with incorrect number of arguments")
|
|
372
|
+
default:
|
|
373
|
+
// Check if this is a type conversion to a function type
|
|
374
|
+
if funIdent != nil {
|
|
375
|
+
if obj := c.pkg.TypesInfo.Uses[funIdent]; obj != nil {
|
|
376
|
+
// Check if the object is a type name
|
|
377
|
+
if typeName, isType := obj.(*types.TypeName); isType {
|
|
378
|
+
// Make sure we have exactly one argument
|
|
379
|
+
if len(exp.Args) == 1 {
|
|
380
|
+
// Check if this is a function type
|
|
381
|
+
if _, isFuncType := typeName.Type().Underlying().(*types.Signature); isFuncType {
|
|
382
|
+
// For function types, we need to add a __goTypeName property
|
|
383
|
+
c.tsw.WriteLiterally("Object.assign(")
|
|
384
|
+
|
|
385
|
+
// Write the argument first
|
|
386
|
+
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
387
|
+
return fmt.Errorf("failed to write argument for function type cast: %w", err)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Add the __goTypeName property with the function type name
|
|
391
|
+
c.tsw.WriteLiterallyf(", { __goTypeName: '%s' })", funIdent.String())
|
|
392
|
+
return nil // Handled function type cast
|
|
393
|
+
} else {
|
|
394
|
+
// For non-function types, use the TypeScript "as" operator
|
|
395
|
+
c.tsw.WriteLiterally("(")
|
|
396
|
+
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
397
|
+
return fmt.Errorf("failed to write argument for type cast: %w", err)
|
|
398
|
+
}
|
|
399
|
+
|
|
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(")")
|
|
404
|
+
return nil // Handled non-function type cast
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Check if this is an async function call
|
|
412
|
+
if funIdent != nil {
|
|
413
|
+
// Get the object for this function identifier
|
|
414
|
+
if obj := c.pkg.TypesInfo.Uses[funIdent]; obj != nil && c.analysis.IsAsyncFunc(obj) {
|
|
415
|
+
// This is an async function
|
|
416
|
+
c.tsw.WriteLiterally("await ")
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Not a special built-in, treat as a regular function call
|
|
421
|
+
if err := c.WriteValueExpr(expFun); err != nil {
|
|
422
|
+
return fmt.Errorf("failed to write function expression in call: %w", err)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if funType := c.pkg.TypesInfo.TypeOf(expFun); funType != nil {
|
|
426
|
+
if _, ok := funType.Underlying().(*types.Signature); ok {
|
|
427
|
+
if _, isNamed := funType.(*types.Named); isNamed {
|
|
428
|
+
c.tsw.WriteLiterally("!")
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
c.tsw.WriteLiterally("(")
|
|
434
|
+
for i, arg := range exp.Args {
|
|
435
|
+
if i != 0 {
|
|
436
|
+
c.tsw.WriteLiterally(", ")
|
|
437
|
+
}
|
|
438
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
439
|
+
return fmt.Errorf("failed to write argument %d in call: %w", i, err)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
c.tsw.WriteLiterally(")")
|
|
443
|
+
return nil // Handled regular function call
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
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
|
+
}
|
|
458
|
+
|
|
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
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
c.tsw.WriteLiterally("(")
|
|
469
|
+
for i, arg := range exp.Args {
|
|
470
|
+
if i != 0 {
|
|
471
|
+
c.tsw.WriteLiterally(", ")
|
|
472
|
+
}
|
|
473
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
474
|
+
return fmt.Errorf("failed to write argument %d in call: %w", i, err)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
c.tsw.WriteLiterally(")")
|
|
478
|
+
return nil
|
|
479
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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 null assertion for selector expressions when accessing fields/methods on nullable types
|
|
95
|
+
// In Go, accessing fields or calling methods on nil pointers/interfaces panics, so we should throw in TypeScript
|
|
96
|
+
baseType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
97
|
+
if baseType != nil {
|
|
98
|
+
// Check if the base is a pointer type
|
|
99
|
+
if _, isPtr := baseType.(*types.Pointer); isPtr {
|
|
100
|
+
c.tsw.WriteLiterally("!.")
|
|
101
|
+
} else if _, isInterface := baseType.Underlying().(*types.Interface); isInterface {
|
|
102
|
+
// For interface types, add null assertion since interfaces can be nil
|
|
103
|
+
c.tsw.WriteLiterally("!.")
|
|
104
|
+
} else if callExpr, isCall := exp.X.(*ast.CallExpr); isCall {
|
|
105
|
+
// For function calls that return nullable types, add null assertion
|
|
106
|
+
_ = callExpr // Use the variable to avoid unused error
|
|
107
|
+
c.tsw.WriteLiterally("!.")
|
|
108
|
+
} else {
|
|
109
|
+
// Add .
|
|
110
|
+
c.tsw.WriteLiterally(".")
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
// Add .
|
|
114
|
+
c.tsw.WriteLiterally(".")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 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)
|
|
124
|
+
return nil
|
|
125
|
+
}
|
|
@@ -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
|
+
}
|