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,591 @@
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
+ // WriteIndexExpr translates a Go index expression (a[b]) to its TypeScript equivalent.
13
+ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
14
+ // Check if this might be a generic function instantiation with a single type argument
15
+ // In this case, the Index should be a type expression, not a value expression
16
+ if tv, ok := c.pkg.TypesInfo.Types[exp.X]; ok {
17
+ // If X is a function type, this might be generic instantiation
18
+ if _, isFuncType := tv.Type.Underlying().(*types.Signature); isFuncType {
19
+ // Check if the index is a type expression (identifier that refers to a type)
20
+ if indexIdent, isIdent := exp.Index.(*ast.Ident); isIdent {
21
+ // Check if this identifier refers to a type
22
+ if obj := c.pkg.TypesInfo.Uses[indexIdent]; obj != nil {
23
+ if _, isTypeName := obj.(*types.TypeName); isTypeName {
24
+ // This is a generic function instantiation: f[T] -> f<T>
25
+ if err := c.WriteValueExpr(exp.X); err != nil {
26
+ return err
27
+ }
28
+ c.tsw.WriteLiterally("<")
29
+ c.WriteTypeExpr(exp.Index)
30
+ c.tsw.WriteLiterally(">")
31
+ return nil
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ // Handle map access: use Map.get() instead of brackets for reading values
39
+ if tv, ok := c.pkg.TypesInfo.Types[exp.X]; ok {
40
+ underlyingType := tv.Type.Underlying()
41
+ // Check if it's a map type
42
+ if mapType, isMap := underlyingType.(*types.Map); isMap {
43
+ c.tsw.WriteLiterally("$.mapGet(")
44
+ if err := c.WriteValueExpr(exp.X); err != nil {
45
+ return err
46
+ }
47
+ c.tsw.WriteLiterally(", ")
48
+ if err := c.WriteValueExpr(exp.Index); err != nil {
49
+ return err
50
+ }
51
+ c.tsw.WriteLiterally(", ")
52
+
53
+ // Generate the zero value as the default value for mapGet
54
+ c.WriteZeroValueForType(mapType.Elem())
55
+ c.tsw.WriteLiterally(")")
56
+ return nil
57
+ }
58
+
59
+ // Check if it's a string type
60
+ if basicType, isBasic := underlyingType.(*types.Basic); isBasic && (basicType.Info()&types.IsString) != 0 {
61
+ c.tsw.WriteLiterally("$.indexString(")
62
+ if err := c.WriteValueExpr(exp.X); err != nil {
63
+ return err
64
+ }
65
+ c.tsw.WriteLiterally(", ")
66
+ if err := c.WriteValueExpr(exp.Index); err != nil {
67
+ return err
68
+ }
69
+ c.tsw.WriteLiterally(")")
70
+ return nil
71
+ }
72
+
73
+ // Check if it's a type parameter with a union constraint (e.g., string | []byte)
74
+ if _, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
75
+ // For type parameters with string | []byte constraint, use specialized function
76
+ // that returns number (byte value) for better TypeScript typing
77
+ c.tsw.WriteLiterally("$.indexStringOrBytes(")
78
+ if err := c.WriteValueExpr(exp.X); err != nil {
79
+ return err
80
+ }
81
+ c.tsw.WriteLiterally(", ")
82
+ if err := c.WriteValueExpr(exp.Index); err != nil {
83
+ return err
84
+ }
85
+ c.tsw.WriteLiterally(")")
86
+ return nil
87
+ }
88
+ }
89
+
90
+ // Regular array/slice access: use brackets
91
+ if err := c.WriteValueExpr(exp.X); err != nil {
92
+ return err
93
+ }
94
+ c.tsw.WriteLiterally("![") // non-null assertion
95
+ if err := c.WriteValueExpr(exp.Index); err != nil {
96
+ return err
97
+ }
98
+ c.tsw.WriteLiterally("]")
99
+ return nil
100
+ }
101
+
102
+ // WriteIndexListExpr translates a Go generic function instantiation (f[T1, T2]) to its TypeScript equivalent (f<T1, T2>).
103
+ func (c *GoToTSCompiler) WriteIndexListExpr(exp *ast.IndexListExpr) error {
104
+ // Write the function expression
105
+ if err := c.WriteValueExpr(exp.X); err != nil {
106
+ return err
107
+ }
108
+
109
+ // Write the type arguments using TypeScript syntax
110
+ c.tsw.WriteLiterally("<")
111
+ for i, typeArg := range exp.Indices {
112
+ if i > 0 {
113
+ c.tsw.WriteLiterally(", ")
114
+ }
115
+ c.WriteTypeExpr(typeArg)
116
+ }
117
+ c.tsw.WriteLiterally(">")
118
+
119
+ return nil
120
+ }
121
+
122
+ // WriteTypeAssertExpr translates a Go type assertion expression (e.g., `x.(T)`)
123
+ // into a TypeScript call to `$.typeAssert<T_ts>(x_ts, 'TypeName').value`.
124
+ // The `$.typeAssert` runtime function handles the actual type check and panic
125
+ // if the assertion fails. The `.value` access is used because in an expression
126
+ // context, we expect the asserted value directly. The `TypeName` string is used
127
+ // by the runtime for error messages.
128
+ func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
129
+ // Generate a call to $.typeAssert
130
+ c.tsw.WriteLiterally("$.mustTypeAssert<")
131
+ c.WriteTypeExpr(exp.Type) // Write the asserted type for the generic
132
+ c.tsw.WriteLiterally(">(")
133
+ if err := c.WriteValueExpr(exp.X); err != nil { // The interface expression
134
+ return fmt.Errorf("failed to write interface expression in type assertion expression: %w", err)
135
+ }
136
+ c.tsw.WriteLiterally(", ")
137
+
138
+ // Unwrap parenthesized expressions to handle cases like r.((<-chan T))
139
+ typeExpr := exp.Type
140
+ for {
141
+ if parenExpr, ok := typeExpr.(*ast.ParenExpr); ok {
142
+ typeExpr = parenExpr.X
143
+ } else {
144
+ break
145
+ }
146
+ }
147
+
148
+ c.writeTypeDescription(typeExpr)
149
+
150
+ c.tsw.WriteLiterally(")")
151
+
152
+ return nil
153
+ }
154
+
155
+ // isPointerComparison checks if a binary expression `exp` involves comparing
156
+ // two pointer types. It uses `go/types` information to determine the types
157
+ // of the left (X) and right (Y) operands of the binary expression.
158
+ // Returns `true` if both operands are determined to be pointer types,
159
+ // `false` otherwise. This is used to apply specific comparison semantics
160
+ // for pointers (e.g., comparing the box objects directly).
161
+ func (c *GoToTSCompiler) isPointerComparison(exp *ast.BinaryExpr) bool {
162
+ leftType := c.pkg.TypesInfo.TypeOf(exp.X)
163
+ rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
164
+ if leftType != nil && rightType != nil {
165
+ if _, leftIsPtr := leftType.(*types.Pointer); leftIsPtr {
166
+ if _, rightIsPtr := rightType.(*types.Pointer); rightIsPtr {
167
+ return true
168
+ }
169
+ }
170
+ }
171
+ return false
172
+ }
173
+
174
+ // getTypeNameString returns a string representation of a Go type expression (`ast.Expr`).
175
+ // It handles simple identifiers (e.g., `MyType`) and selector expressions
176
+ // (e.g., `pkg.Type`). For more complex or unrecognized type expressions,
177
+ // it returns "unknown". This string is primarily used for runtime error messages,
178
+ // such as in type assertions.
179
+ func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
180
+ switch t := typeExpr.(type) {
181
+ case *ast.Ident:
182
+ return t.Name
183
+ case *ast.SelectorExpr:
184
+ // For imported types like pkg.Type
185
+ if ident, ok := t.X.(*ast.Ident); ok {
186
+ return fmt.Sprintf("%s.%s", ident.Name, t.Sel.Name)
187
+ }
188
+ }
189
+ // Default case, use a placeholder for complex types
190
+ return "unknown"
191
+ }
192
+
193
+ // WriteBinaryExpr translates a Go binary expression (`ast.BinaryExpr`) into its
194
+ // TypeScript equivalent.
195
+ // It handles several cases:
196
+ // - Channel send (`ch <- val`): Becomes `await ch.send(val)`.
197
+ // - Nil comparison for pointers (`ptr == nil` or `ptr != nil`): Compares the
198
+ // pointer (which may be a box object or `null`) directly to `null` using
199
+ // the translated operator (`==` or `!=`).
200
+ // - Pointer comparison (non-nil, `ptr1 == ptr2` or `ptr1 != ptr2`): Compares
201
+ // the box objects directly using strict equality (`===` or `!==`).
202
+ // - Bitwise operations (`&`, `|`, `^`, `<<`, `>>`, `&^`): The expression is wrapped
203
+ // in parentheses `()` to ensure correct precedence in TypeScript, and operators
204
+ // are mapped (e.g., `&^` might need special handling or is mapped to a runtime helper).
205
+ // - Other binary operations (arithmetic, logical, comparison): Operands are
206
+ // translated using `WriteValueExpr`, and the operator is mapped to its TypeScript
207
+ // equivalent using `TokenToTs`.
208
+ //
209
+ // Unhandled operators result in a comment and a placeholder.
210
+ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
211
+ // Handle special cases like channel send
212
+ if exp.Op == token.ARROW {
213
+ // Channel send: ch <- val becomes await $.chanSend(ch, val)
214
+ c.tsw.WriteLiterally("await $.chanSend(")
215
+ if err := c.WriteValueExpr(exp.X); err != nil {
216
+ return fmt.Errorf("failed to write channel send target: %w", err)
217
+ }
218
+ c.tsw.WriteLiterally(", ")
219
+ if err := c.WriteValueExpr(exp.Y); err != nil {
220
+ return fmt.Errorf("failed to write channel send value: %w", err)
221
+ }
222
+ c.tsw.WriteLiterally(")")
223
+ return nil
224
+ }
225
+
226
+ // Check if this is a nil comparison for a pointer
227
+ isNilComparison := false
228
+ var ptrExpr ast.Expr
229
+ if (exp.Op == token.EQL || exp.Op == token.NEQ) && c.pkg != nil && c.pkg.TypesInfo != nil {
230
+ if leftIdent, ok := exp.Y.(*ast.Ident); ok && leftIdent.Name == "nil" {
231
+ leftType := c.pkg.TypesInfo.TypeOf(exp.X)
232
+ if _, isPtr := leftType.(*types.Pointer); isPtr {
233
+ isNilComparison = true
234
+ ptrExpr = exp.X
235
+ }
236
+ } else if rightIdent, ok := exp.X.(*ast.Ident); ok && rightIdent.Name == "nil" {
237
+ rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
238
+ if _, isPtr := rightType.(*types.Pointer); isPtr {
239
+ isNilComparison = true
240
+ ptrExpr = exp.Y
241
+ }
242
+ }
243
+ }
244
+
245
+ if isNilComparison {
246
+ // Compare the box object directly to null
247
+ if err := c.WriteValueExpr(ptrExpr); err != nil {
248
+ return fmt.Errorf("failed to write pointer expression in nil comparison: %w", err)
249
+ }
250
+ c.tsw.WriteLiterally(" ")
251
+ tokStr, ok := TokenToTs(exp.Op)
252
+ if !ok {
253
+ return errors.Errorf("unhandled binary op: %s", exp.Op.String())
254
+ }
255
+ c.tsw.WriteLiterally(tokStr)
256
+ c.tsw.WriteLiterally(" null")
257
+ return nil
258
+ }
259
+
260
+ // Check if this is a pointer comparison (non-nil)
261
+ // Compare the box objects directly using === or !==
262
+ if c.isPointerComparison(exp) {
263
+ c.tsw.WriteLiterally("(") // Wrap comparison
264
+ if err := c.WriteValueExpr(exp.X); err != nil {
265
+ return fmt.Errorf("failed to write binary expression left operand: %w", err)
266
+ }
267
+ c.tsw.WriteLiterally(" ")
268
+ // Use === for == and !== for !=
269
+ tokStr := ""
270
+ switch exp.Op {
271
+ case token.EQL:
272
+ tokStr = "==="
273
+ case token.NEQ:
274
+ tokStr = "!=="
275
+ default:
276
+ return errors.Errorf("unhandled pointer comparison op: %s", exp.Op.String())
277
+ }
278
+ c.tsw.WriteLiterally(tokStr)
279
+ c.tsw.WriteLiterally(" ")
280
+ if err := c.WriteValueExpr(exp.Y); err != nil {
281
+ return fmt.Errorf("failed to write binary expression right operand: %w", err)
282
+ }
283
+ c.tsw.WriteLiterally(")") // Close wrap
284
+ return nil
285
+ }
286
+
287
+ // Check if the operator is a bitwise operator
288
+ isBitwise := false
289
+ switch exp.Op {
290
+ case token.AND, token.OR, token.XOR, token.SHL, token.SHR, token.AND_NOT:
291
+ isBitwise = true
292
+ }
293
+
294
+ // Special handling for large bit shift expressions that would overflow in JavaScript
295
+ if exp.Op == token.SHL {
296
+ // Check if this is 1 << 63 pattern
297
+ if leftLit, leftIsLit := exp.X.(*ast.BasicLit); leftIsLit && leftLit.Value == "1" {
298
+ if rightLit, rightIsLit := exp.Y.(*ast.BasicLit); rightIsLit && rightLit.Value == "63" {
299
+ // Replace 1 << 63 with Number.MAX_SAFE_INTEGER (9007199254740991)
300
+ // This is the largest integer that can be exactly represented in JavaScript
301
+ c.tsw.WriteLiterally("Number.MAX_SAFE_INTEGER")
302
+ return nil
303
+ }
304
+ }
305
+ }
306
+
307
+ if isBitwise {
308
+ c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
309
+ }
310
+
311
+ if err := c.WriteValueExpr(exp.X); err != nil {
312
+ return fmt.Errorf("failed to write binary expression left operand: %w", err)
313
+ }
314
+ c.tsw.WriteLiterally(" ")
315
+ tokStr, ok := TokenToTs(exp.Op)
316
+ if !ok {
317
+ return errors.Errorf("unhandled binary op: %s", exp.Op.String())
318
+ }
319
+ c.tsw.WriteLiterally(tokStr)
320
+ c.tsw.WriteLiterally(" ")
321
+ if err := c.WriteValueExpr(exp.Y); err != nil {
322
+ return fmt.Errorf("failed to write binary expression right operand: %w", err)
323
+ }
324
+
325
+ if isBitwise {
326
+ c.tsw.WriteLiterally(")") // Add closing parenthesis for bitwise operations
327
+ }
328
+
329
+ return nil
330
+ }
331
+
332
+ // WriteUnaryExpr translates a Go unary expression (`ast.UnaryExpr`) into its
333
+ // TypeScript equivalent.
334
+ // It handles several unary operations:
335
+ // - Channel receive (`<-ch`): Becomes `await ch.receive()`.
336
+ // - Address-of (`&var`):
337
+ // - If `var` is a boxed variable (its address was taken), `&var` evaluates
338
+ // to the box itself (i.e., `varName` in TypeScript, which holds the box).
339
+ // - Otherwise (e.g., `&unboxedVar`, `&MyStruct{}`, `&FuncCall()`), it evaluates
340
+ // the operand `var`. The resulting TypeScript value (e.g., a new object instance)
341
+ // acts as the "pointer". Boxing decisions for such pointers are handled at
342
+ // the assignment site.
343
+ // - Other unary operators (`+`, `-`, `!`, `^`): Mapped to their TypeScript
344
+ // equivalents (e.g., `+`, `-`, `!`, `~` for bitwise NOT). Parentheses are added
345
+ // around the operand if it's a binary or unary expression to maintain precedence.
346
+ //
347
+ // Unhandled operators result in a comment and an attempt to write the operator
348
+ // token directly. Postfix operators (`++`, `--`) are expected to be handled by
349
+ // their statement contexts (e.g., `IncDecStmt`).
350
+ func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
351
+ if exp.Op == token.ARROW {
352
+ // Channel receive: <-ch becomes await $.chanRecv(ch)
353
+ c.tsw.WriteLiterally("await $.chanRecv(")
354
+ if err := c.WriteValueExpr(exp.X); err != nil {
355
+ return fmt.Errorf("failed to write channel receive operand: %w", err)
356
+ }
357
+ c.tsw.WriteLiterally(")")
358
+ return nil
359
+ }
360
+
361
+ if exp.Op == token.AND { // Address-of operator (&)
362
+ // If the operand is an identifier for a variable that is boxed,
363
+ // the result of & is the box itself.
364
+ if ident, ok := exp.X.(*ast.Ident); ok {
365
+ var obj types.Object
366
+ obj = c.pkg.TypesInfo.Uses[ident]
367
+ if obj == nil {
368
+ obj = c.pkg.TypesInfo.Defs[ident]
369
+ }
370
+ if obj != nil && c.analysis.NeedsBoxed(obj) {
371
+ // &boxedVar -> boxedVar (the box itself)
372
+ c.tsw.WriteLiterally(ident.Name) // Write the identifier name (which holds the box)
373
+ return nil
374
+ }
375
+ }
376
+
377
+ // Otherwise (&unboxedVar, &CompositeLit{}, &FuncCall(), etc.),
378
+ // the address-of operator in Go, when used to create a pointer,
379
+ // translates to simply evaluating the operand in TypeScript.
380
+ // The resulting value (e.g., a new object instance) acts as the "pointer".
381
+ // Boxing decisions are handled at the assignment site based on the LHS variable.
382
+ if err := c.WriteValueExpr(exp.X); err != nil {
383
+ return fmt.Errorf("failed to write &-operand: %w", err)
384
+ }
385
+
386
+ return nil
387
+ }
388
+
389
+ // Handle other unary operators (+, -, !, ^)
390
+ tokStr, ok := TokenToTs(exp.Op)
391
+ if !ok {
392
+ return errors.Errorf("unhandled unary op: %s", exp.Op.String())
393
+ }
394
+ c.tsw.WriteLiterally(tokStr)
395
+
396
+ // Add space if operator is not postfix (e.g., !)
397
+ if exp.Op != token.INC && exp.Op != token.DEC {
398
+ // Check if operand needs parentheses (e.g., !(-a))
399
+ // Basic check: if operand is binary or unary, add parens
400
+ needsParens := false
401
+ switch exp.X.(type) {
402
+ case *ast.BinaryExpr, *ast.UnaryExpr:
403
+ needsParens = true
404
+ }
405
+ if needsParens {
406
+ c.tsw.WriteLiterally("(")
407
+ }
408
+ if err := c.WriteValueExpr(exp.X); err != nil {
409
+ return fmt.Errorf("failed to write unary expression operand: %w", err)
410
+ }
411
+ if needsParens {
412
+ c.tsw.WriteLiterally(")")
413
+ }
414
+ } else {
415
+ // Postfix operators (++, --) - operand written first by caller (e.g., IncDecStmt)
416
+ // This function shouldn't be called directly for ++/-- in expression context in valid Go?
417
+ // If it is, write the operand.
418
+ if err := c.WriteValueExpr(exp.X); err != nil {
419
+ return fmt.Errorf("failed to write unary expression operand for postfix op: %w", err)
420
+ }
421
+ }
422
+
423
+ return nil
424
+ }
425
+
426
+ // WriteSliceExpr translates a Go slice expression (e.g., `s[low:high:max]`) to its TypeScript equivalent.
427
+ // If `s` is a string and it's not a 3-index slice, it uses `s.substring(low, high)`.
428
+ // If `s` is `[]byte` (Uint8Array) and it's not a 3-index slice, it uses `s.subarray(low, high)`.
429
+ // Otherwise, it falls back to the `$.goSlice(s, low, high, max)` runtime helper.
430
+ func (c *GoToTSCompiler) WriteSliceExpr(exp *ast.SliceExpr) error {
431
+ // Check if the expression being sliced is a string
432
+ tv := c.pkg.TypesInfo.TypeOf(exp.X)
433
+ isString := false
434
+ isByteSlice := false
435
+ isTypeParam := false
436
+ if tv != nil {
437
+ if basicType, isBasic := tv.Underlying().(*types.Basic); isBasic && (basicType.Info()&types.IsString) != 0 {
438
+ isString = true
439
+ }
440
+ if sliceType, isSlice := tv.Underlying().(*types.Slice); isSlice {
441
+ if basicElem, isBasic := sliceType.Elem().(*types.Basic); isBasic && basicElem.Kind() == types.Uint8 {
442
+ isByteSlice = true
443
+ }
444
+ }
445
+ if _, isTP := tv.(*types.TypeParam); isTP {
446
+ isTypeParam = true
447
+ }
448
+ }
449
+
450
+ // Handle type parameters with union constraints (e.g., string | []byte)
451
+ if isTypeParam {
452
+ // For type parameters, we need to create a runtime helper that handles both string and []byte
453
+ c.tsw.WriteLiterally("$.sliceStringOrBytes(")
454
+ if err := c.WriteValueExpr(exp.X); err != nil {
455
+ return err
456
+ }
457
+ c.tsw.WriteLiterally(", ")
458
+ if exp.Low != nil {
459
+ if err := c.WriteValueExpr(exp.Low); err != nil {
460
+ return err
461
+ }
462
+ } else {
463
+ c.tsw.WriteLiterally("undefined")
464
+ }
465
+ c.tsw.WriteLiterally(", ")
466
+ if exp.High != nil {
467
+ if err := c.WriteValueExpr(exp.High); err != nil {
468
+ return err
469
+ }
470
+ } else {
471
+ c.tsw.WriteLiterally("undefined")
472
+ }
473
+ if exp.Slice3 {
474
+ c.tsw.WriteLiterally(", ")
475
+ if exp.Max != nil {
476
+ if err := c.WriteValueExpr(exp.Max); err != nil {
477
+ return err
478
+ }
479
+ } else {
480
+ c.tsw.WriteLiterally("undefined")
481
+ }
482
+ }
483
+ c.tsw.WriteLiterally(")")
484
+ return nil
485
+ }
486
+
487
+ if isString && !exp.Slice3 {
488
+ // Use $.sliceString for byte-correct string slicing
489
+ c.tsw.WriteLiterally("$.sliceString(")
490
+ if err := c.WriteValueExpr(exp.X); err != nil {
491
+ return err
492
+ }
493
+ c.tsw.WriteLiterally(", ")
494
+ if exp.Low != nil {
495
+ if err := c.WriteValueExpr(exp.Low); err != nil {
496
+ return err
497
+ }
498
+ } else {
499
+ // Go's default low for string[:high] is 0.
500
+ // $.sliceString can handle undefined for low as 0.
501
+ c.tsw.WriteLiterally("undefined")
502
+ }
503
+ c.tsw.WriteLiterally(", ")
504
+ if exp.High != nil {
505
+ if err := c.WriteValueExpr(exp.High); err != nil {
506
+ return err
507
+ }
508
+ } else {
509
+ // Go's default high for string[low:] means to the end.
510
+ // $.sliceString can handle undefined for high as end of string.
511
+ c.tsw.WriteLiterally("undefined")
512
+ }
513
+ c.tsw.WriteLiterally(")")
514
+ } else if isByteSlice && !exp.Slice3 {
515
+ // Use s.subarray(low, high) for []byte slices
516
+ if err := c.WriteValueExpr(exp.X); err != nil {
517
+ return err
518
+ }
519
+ c.tsw.WriteLiterally(".subarray(")
520
+ if exp.Low != nil {
521
+ if err := c.WriteValueExpr(exp.Low); err != nil {
522
+ return err
523
+ }
524
+ } else {
525
+ c.tsw.WriteLiterally("0") // Default low for subarray is 0
526
+ }
527
+ if exp.High != nil {
528
+ c.tsw.WriteLiterally(", ")
529
+ if err := c.WriteValueExpr(exp.High); err != nil {
530
+ return err
531
+ }
532
+ } else {
533
+ // If high is omitted, subarray goes to the end of the array.
534
+ // No need to write undefined or length, just close the parenthesis if low was the last arg.
535
+ }
536
+ c.tsw.WriteLiterally(")")
537
+ } else {
538
+ // Fallback to $.goSlice for actual slices (arrays) or 3-index string slices (which are rare and might need $.goSlice's complexity)
539
+ // Or if it's a string but has Slice3, it's not handled by simple substring.
540
+ c.tsw.WriteLiterally("$.goSlice(")
541
+ if err := c.WriteValueExpr(exp.X); err != nil {
542
+ return err
543
+ }
544
+ c.tsw.WriteLiterally(", ")
545
+ if exp.Low != nil {
546
+ if err := c.WriteValueExpr(exp.Low); err != nil {
547
+ return err
548
+ }
549
+ } else {
550
+ c.tsw.WriteLiterally("undefined")
551
+ }
552
+ c.tsw.WriteLiterally(", ")
553
+ if exp.High != nil {
554
+ if err := c.WriteValueExpr(exp.High); err != nil {
555
+ return err
556
+ }
557
+ } else {
558
+ c.tsw.WriteLiterally("undefined")
559
+ }
560
+ if exp.Slice3 {
561
+ c.tsw.WriteLiterally(", ")
562
+ if exp.Max != nil {
563
+ if err := c.WriteValueExpr(exp.Max); err != nil {
564
+ return err
565
+ }
566
+ } else {
567
+ c.tsw.WriteLiterally("undefined")
568
+ }
569
+ }
570
+ c.tsw.WriteLiterally(")")
571
+ }
572
+ return nil
573
+ }
574
+
575
+ // WriteKeyValueExpr translates a Go key-value pair expression (`ast.KeyValueExpr`),
576
+ // typically found within composite literals (for structs, maps, or arrays with
577
+ // indexed elements), into its TypeScript object property equivalent: `key: value`.
578
+ // Both the key and the value expressions are recursively translated using
579
+ // `WriteValueExpr`. The original Go casing for keys is preserved.
580
+ // For example, `MyField: 123` in Go becomes `MyField: 123` in TypeScript.
581
+ func (c *GoToTSCompiler) WriteKeyValueExpr(exp *ast.KeyValueExpr) error {
582
+ // Keep original Go casing for keys
583
+ if err := c.WriteValueExpr(exp.Key); err != nil {
584
+ return fmt.Errorf("failed to write key-value expression key: %w", err)
585
+ }
586
+ c.tsw.WriteLiterally(": ")
587
+ if err := c.WriteValueExpr(exp.Value); err != nil {
588
+ return fmt.Errorf("failed to write key-value expression value: %w", err)
589
+ }
590
+ return nil
591
+ }