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.
@@ -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
+ }