goscript 0.0.22 → 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 +6 -1
- package/compiler/compiler.go +41 -18
- package/compiler/compiler_test.go +36 -8
- package/compiler/composite-lit.go +25 -10
- package/compiler/decl.go +36 -0
- package/compiler/expr-call.go +116 -60
- package/compiler/expr-selector.go +22 -2
- package/compiler/expr-type.go +131 -4
- package/compiler/expr-value.go +7 -37
- package/compiler/expr.go +247 -12
- package/compiler/field.go +3 -3
- package/compiler/lit.go +34 -2
- package/compiler/primitive.go +8 -2
- package/compiler/spec-struct.go +137 -6
- package/compiler/spec-value.go +50 -18
- package/compiler/spec.go +12 -3
- package/compiler/stmt-assign.go +29 -1
- package/compiler/stmt-range.go +9 -11
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +129 -244
- package/compiler/type-assert.go +125 -379
- package/compiler/type.go +187 -125
- package/package.json +5 -5
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2379
- package/dist/builtin/builtin.d.ts +0 -513
- package/dist/builtin/builtin.js +0 -1686
- package/dist/builtin/builtin.js.map +0 -1
package/compiler/expr.go
CHANGED
|
@@ -11,10 +11,35 @@ import (
|
|
|
11
11
|
|
|
12
12
|
// WriteIndexExpr translates a Go index expression (a[b]) to its TypeScript equivalent.
|
|
13
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
|
+
|
|
14
38
|
// Handle map access: use Map.get() instead of brackets for reading values
|
|
15
39
|
if tv, ok := c.pkg.TypesInfo.Types[exp.X]; ok {
|
|
40
|
+
underlyingType := tv.Type.Underlying()
|
|
16
41
|
// Check if it's a map type
|
|
17
|
-
if mapType, isMap :=
|
|
42
|
+
if mapType, isMap := underlyingType.(*types.Map); isMap {
|
|
18
43
|
c.tsw.WriteLiterally("$.mapGet(")
|
|
19
44
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
20
45
|
return err
|
|
@@ -30,6 +55,36 @@ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
|
30
55
|
c.tsw.WriteLiterally(")")
|
|
31
56
|
return nil
|
|
32
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
|
+
}
|
|
33
88
|
}
|
|
34
89
|
|
|
35
90
|
// Regular array/slice access: use brackets
|
|
@@ -44,6 +99,26 @@ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
|
44
99
|
return nil
|
|
45
100
|
}
|
|
46
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
|
+
|
|
47
122
|
// WriteTypeAssertExpr translates a Go type assertion expression (e.g., `x.(T)`)
|
|
48
123
|
// into a TypeScript call to `$.typeAssert<T_ts>(x_ts, 'TypeName').value`.
|
|
49
124
|
// The `$.typeAssert` runtime function handles the actual type check and panic
|
|
@@ -52,7 +127,7 @@ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
|
52
127
|
// by the runtime for error messages.
|
|
53
128
|
func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
|
|
54
129
|
// Generate a call to $.typeAssert
|
|
55
|
-
c.tsw.WriteLiterally("$.
|
|
130
|
+
c.tsw.WriteLiterally("$.mustTypeAssert<")
|
|
56
131
|
c.WriteTypeExpr(exp.Type) // Write the asserted type for the generic
|
|
57
132
|
c.tsw.WriteLiterally(">(")
|
|
58
133
|
if err := c.WriteValueExpr(exp.X); err != nil { // The interface expression
|
|
@@ -60,9 +135,6 @@ func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
|
|
|
60
135
|
}
|
|
61
136
|
c.tsw.WriteLiterally(", ")
|
|
62
137
|
|
|
63
|
-
// Write the type description instead of just the type name
|
|
64
|
-
// This ensures we generate proper type info objects for all types
|
|
65
|
-
|
|
66
138
|
// Unwrap parenthesized expressions to handle cases like r.((<-chan T))
|
|
67
139
|
typeExpr := exp.Type
|
|
68
140
|
for {
|
|
@@ -75,7 +147,8 @@ func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
|
|
|
75
147
|
|
|
76
148
|
c.writeTypeDescription(typeExpr)
|
|
77
149
|
|
|
78
|
-
c.tsw.WriteLiterally(")")
|
|
150
|
+
c.tsw.WriteLiterally(")")
|
|
151
|
+
|
|
79
152
|
return nil
|
|
80
153
|
}
|
|
81
154
|
|
|
@@ -137,12 +210,12 @@ func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
|
|
|
137
210
|
func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
138
211
|
// Handle special cases like channel send
|
|
139
212
|
if exp.Op == token.ARROW {
|
|
140
|
-
// Channel send: ch <- val becomes await ch
|
|
141
|
-
c.tsw.WriteLiterally("await ")
|
|
213
|
+
// Channel send: ch <- val becomes await $.chanSend(ch, val)
|
|
214
|
+
c.tsw.WriteLiterally("await $.chanSend(")
|
|
142
215
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
143
216
|
return fmt.Errorf("failed to write channel send target: %w", err)
|
|
144
217
|
}
|
|
145
|
-
c.tsw.WriteLiterally("
|
|
218
|
+
c.tsw.WriteLiterally(", ")
|
|
146
219
|
if err := c.WriteValueExpr(exp.Y); err != nil {
|
|
147
220
|
return fmt.Errorf("failed to write channel send value: %w", err)
|
|
148
221
|
}
|
|
@@ -218,6 +291,19 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
218
291
|
isBitwise = true
|
|
219
292
|
}
|
|
220
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
|
+
|
|
221
307
|
if isBitwise {
|
|
222
308
|
c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
|
|
223
309
|
}
|
|
@@ -263,12 +349,12 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
263
349
|
// their statement contexts (e.g., `IncDecStmt`).
|
|
264
350
|
func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
|
|
265
351
|
if exp.Op == token.ARROW {
|
|
266
|
-
// Channel receive: <-ch becomes await ch
|
|
267
|
-
c.tsw.WriteLiterally("await ")
|
|
352
|
+
// Channel receive: <-ch becomes await $.chanRecv(ch)
|
|
353
|
+
c.tsw.WriteLiterally("await $.chanRecv(")
|
|
268
354
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
269
355
|
return fmt.Errorf("failed to write channel receive operand: %w", err)
|
|
270
356
|
}
|
|
271
|
-
c.tsw.WriteLiterally("
|
|
357
|
+
c.tsw.WriteLiterally(")")
|
|
272
358
|
return nil
|
|
273
359
|
}
|
|
274
360
|
|
|
@@ -337,6 +423,155 @@ func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
|
|
|
337
423
|
return nil
|
|
338
424
|
}
|
|
339
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
|
+
|
|
340
575
|
// WriteKeyValueExpr translates a Go key-value pair expression (`ast.KeyValueExpr`),
|
|
341
576
|
// typically found within composite literals (for structs, maps, or arrays with
|
|
342
577
|
// indexed elements), into its TypeScript object property equivalent: `key: value`.
|
package/compiler/field.go
CHANGED
|
@@ -50,7 +50,7 @@ func (c *GoToTSCompiler) WriteFieldList(a *ast.FieldList, isArguments bool) {
|
|
|
50
50
|
c.tsw.WriteLiterally(name.Name)
|
|
51
51
|
c.tsw.WriteLiterally(": ")
|
|
52
52
|
typ := c.pkg.TypesInfo.TypeOf(field.Type)
|
|
53
|
-
c.WriteGoType(typ)
|
|
53
|
+
c.WriteGoType(typ, GoTypeContextGeneral)
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -89,7 +89,7 @@ func (c *GoToTSCompiler) WriteFieldList(a *ast.FieldList, isArguments bool) {
|
|
|
89
89
|
c.tsw.WriteLiterally(name.Name)
|
|
90
90
|
c.tsw.WriteLiterally(": ")
|
|
91
91
|
typ := c.pkg.TypesInfo.TypeOf(field.Type)
|
|
92
|
-
c.WriteGoType(typ) // Use WriteGoType for parameter type
|
|
92
|
+
c.WriteGoType(typ, GoTypeContextGeneral) // Use WriteGoType for parameter type
|
|
93
93
|
}
|
|
94
94
|
} else {
|
|
95
95
|
// For struct fields and other non-argument fields
|
|
@@ -155,7 +155,7 @@ func (c *GoToTSCompiler) WriteField(field *ast.Field, isArguments bool) {
|
|
|
155
155
|
// write type for struct fields (not arguments)
|
|
156
156
|
c.tsw.WriteLiterally(": ")
|
|
157
157
|
typ := c.pkg.TypesInfo.TypeOf(field.Type)
|
|
158
|
-
c.WriteGoType(typ) // Use WriteGoType for field type
|
|
158
|
+
c.WriteGoType(typ, GoTypeContextGeneral) // Use WriteGoType for field type
|
|
159
159
|
|
|
160
160
|
if !isArguments {
|
|
161
161
|
// write tag comment if any for struct fields
|
package/compiler/lit.go
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package compiler
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"fmt"
|
|
4
5
|
"go/ast"
|
|
5
6
|
"go/token"
|
|
6
7
|
"strconv"
|
|
@@ -90,9 +91,40 @@ func (c *GoToTSCompiler) WriteFuncLitValue(exp *ast.FuncLit) error {
|
|
|
90
91
|
|
|
91
92
|
c.tsw.WriteLiterally(" => ")
|
|
92
93
|
|
|
94
|
+
hasNamedReturns := false
|
|
95
|
+
if exp.Type.Results != nil {
|
|
96
|
+
for _, field := range exp.Type.Results.List {
|
|
97
|
+
if len(field.Names) > 0 {
|
|
98
|
+
hasNamedReturns = true
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if hasNamedReturns {
|
|
105
|
+
c.tsw.WriteLine("{")
|
|
106
|
+
c.tsw.Indent(1)
|
|
107
|
+
|
|
108
|
+
// Declare named return variables and initialize them to their zero values
|
|
109
|
+
for _, field := range exp.Type.Results.List {
|
|
110
|
+
for _, name := range field.Names {
|
|
111
|
+
c.tsw.WriteLiterallyf("let %s: ", name.Name)
|
|
112
|
+
c.WriteTypeExpr(field.Type)
|
|
113
|
+
c.tsw.WriteLiterally(" = ")
|
|
114
|
+
c.WriteZeroValueForType(c.pkg.TypesInfo.TypeOf(field.Type))
|
|
115
|
+
c.tsw.WriteLine("")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
93
120
|
// Write function body
|
|
94
|
-
if err := c.
|
|
95
|
-
return err
|
|
121
|
+
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
122
|
+
return fmt.Errorf("failed to write block statement: %w", err)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if hasNamedReturns {
|
|
126
|
+
c.tsw.Indent(-1)
|
|
127
|
+
c.tsw.WriteLiterally("}")
|
|
96
128
|
}
|
|
97
129
|
|
|
98
130
|
return nil
|
package/compiler/primitive.go
CHANGED
|
@@ -29,7 +29,10 @@ var goToTypescriptPrimitives = map[string]string{
|
|
|
29
29
|
"int16": "number",
|
|
30
30
|
"int32": "number",
|
|
31
31
|
"rune": "number", // alias for int32
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
// TODO: add bigint support
|
|
34
|
+
// "int64": "bigint", // Requires TypeScript target >= ES2020
|
|
35
|
+
"int64": "number",
|
|
33
36
|
|
|
34
37
|
// Unsigned Integers
|
|
35
38
|
"uint": "number",
|
|
@@ -37,7 +40,10 @@ var goToTypescriptPrimitives = map[string]string{
|
|
|
37
40
|
"byte": "number",
|
|
38
41
|
"uint16": "number",
|
|
39
42
|
"uint32": "number",
|
|
40
|
-
|
|
43
|
+
|
|
44
|
+
// TODO: add bigint support
|
|
45
|
+
// "uint64": "bigint", // Requires TypeScript target >= ES2020
|
|
46
|
+
"uint64": "number",
|
|
41
47
|
|
|
42
48
|
// Floating Point Numbers
|
|
43
49
|
"float32": "number",
|
package/compiler/spec-struct.go
CHANGED
|
@@ -4,6 +4,7 @@ import (
|
|
|
4
4
|
"fmt"
|
|
5
5
|
"go/ast"
|
|
6
6
|
"go/types"
|
|
7
|
+
"sort"
|
|
7
8
|
"strings"
|
|
8
9
|
)
|
|
9
10
|
|
|
@@ -23,6 +24,12 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
23
24
|
if err := c.WriteValueExpr(a.Name); err != nil {
|
|
24
25
|
return err
|
|
25
26
|
}
|
|
27
|
+
|
|
28
|
+
// Write type parameters if present (for generics)
|
|
29
|
+
if a.TypeParams != nil {
|
|
30
|
+
c.WriteTypeParameters(a.TypeParams)
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
c.tsw.WriteLiterally(" ")
|
|
27
34
|
c.tsw.WriteLine("{")
|
|
28
35
|
c.tsw.Indent(1)
|
|
@@ -121,9 +128,25 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
121
128
|
c.tsw.WriteLine("")
|
|
122
129
|
|
|
123
130
|
// Generate the clone method
|
|
124
|
-
|
|
131
|
+
cloneReturnType := className
|
|
132
|
+
if a.TypeParams != nil && len(a.TypeParams.List) > 0 {
|
|
133
|
+
cloneReturnType += "<"
|
|
134
|
+
first := true
|
|
135
|
+
for _, field := range a.TypeParams.List {
|
|
136
|
+
for _, name := range field.Names {
|
|
137
|
+
if !first {
|
|
138
|
+
cloneReturnType += ", "
|
|
139
|
+
}
|
|
140
|
+
first = false
|
|
141
|
+
cloneReturnType += name.Name
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
cloneReturnType += ">"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
c.tsw.WriteLinef("public clone(): %s {", cloneReturnType)
|
|
125
148
|
c.tsw.Indent(1)
|
|
126
|
-
c.tsw.WriteLinef("const cloned = new %s()",
|
|
149
|
+
c.tsw.WriteLinef("const cloned = new %s()", cloneReturnType)
|
|
127
150
|
c.tsw.WriteLine("cloned._fields = {")
|
|
128
151
|
c.tsw.Indent(1)
|
|
129
152
|
|
|
@@ -164,7 +187,18 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
164
187
|
if starExpr, ok := recvType.(*ast.StarExpr); ok {
|
|
165
188
|
recvType = starExpr.X
|
|
166
189
|
}
|
|
167
|
-
|
|
190
|
+
|
|
191
|
+
// Check for both simple identifiers (Pair) and generic types (Pair[T])
|
|
192
|
+
var recvTypeName string
|
|
193
|
+
if ident, ok := recvType.(*ast.Ident); ok {
|
|
194
|
+
recvTypeName = ident.Name
|
|
195
|
+
} else if indexExpr, ok := recvType.(*ast.IndexExpr); ok {
|
|
196
|
+
if ident, ok := indexExpr.X.(*ast.Ident); ok {
|
|
197
|
+
recvTypeName = ident.Name
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if recvTypeName == className {
|
|
168
202
|
c.tsw.WriteLine("")
|
|
169
203
|
if err := c.WriteFuncDeclAsMethod(funcDecl); err != nil {
|
|
170
204
|
return err
|
|
@@ -292,21 +326,21 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
292
326
|
}
|
|
293
327
|
c.tsw.WriteLiterally(paramName)
|
|
294
328
|
c.tsw.WriteLiterally(": ")
|
|
295
|
-
c.WriteGoType(param.Type())
|
|
329
|
+
c.WriteGoType(param.Type(), GoTypeContextGeneral)
|
|
296
330
|
}
|
|
297
331
|
c.tsw.WriteLiterally(")")
|
|
298
332
|
results := sig.Results()
|
|
299
333
|
if results.Len() > 0 {
|
|
300
334
|
c.tsw.WriteLiterally(": ")
|
|
301
335
|
if results.Len() == 1 {
|
|
302
|
-
c.WriteGoType(results.At(0).Type())
|
|
336
|
+
c.WriteGoType(results.At(0).Type(), GoTypeContextFunctionReturn)
|
|
303
337
|
} else {
|
|
304
338
|
c.tsw.WriteLiterally("[")
|
|
305
339
|
for j := 0; j < results.Len(); j++ {
|
|
306
340
|
if j > 0 {
|
|
307
341
|
c.tsw.WriteLiterally(", ")
|
|
308
342
|
}
|
|
309
|
-
c.WriteGoType(results.At(j).Type())
|
|
343
|
+
c.WriteGoType(results.At(j).Type(), GoTypeContextFunctionReturn)
|
|
310
344
|
}
|
|
311
345
|
c.tsw.WriteLiterally("]")
|
|
312
346
|
}
|
|
@@ -380,3 +414,100 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
380
414
|
c.tsw.WriteLine("}")
|
|
381
415
|
return nil
|
|
382
416
|
}
|
|
417
|
+
|
|
418
|
+
// generateFlattenedInitTypeString generates a TypeScript type string for the
|
|
419
|
+
// initialization object passed to a Go struct's constructor (`_init` method in TypeScript).
|
|
420
|
+
// The generated type is a `Partial`-like structure, `"{ Field1?: Type1, Field2?: Type2, ... }"`,
|
|
421
|
+
// including all direct and promoted fields of the `structType`.
|
|
422
|
+
// - It iterates through the direct fields of the `structType`. Exported fields
|
|
423
|
+
// are included with their TypeScript types (obtained via `WriteGoType`).
|
|
424
|
+
// - For anonymous (embedded) fields, it generates a type like `EmbeddedName?: ConstructorParameters<typeof EmbeddedName>[0]`,
|
|
425
|
+
// allowing initialization of the embedded struct's fields directly within the outer struct's initializer.
|
|
426
|
+
// - It then uses `types.NewMethodSet` and checks for `types.Var` objects that are fields
|
|
427
|
+
// to find promoted fields from embedded structs, adding them to the type string if not already present.
|
|
428
|
+
//
|
|
429
|
+
// The resulting string is sorted by field name for deterministic output and represents
|
|
430
|
+
// the shape of the object expected by the struct's TypeScript constructor.
|
|
431
|
+
func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named) string {
|
|
432
|
+
if structType == nil {
|
|
433
|
+
return "{}"
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Use a map to collect unique field names and their types
|
|
437
|
+
fieldMap := make(map[string]string)
|
|
438
|
+
embeddedTypeMap := make(map[string]string) // Stores TS type string for embedded struct initializers
|
|
439
|
+
|
|
440
|
+
underlying, ok := structType.Underlying().(*types.Struct)
|
|
441
|
+
if !ok {
|
|
442
|
+
return "{}"
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// First add the direct fields and track embedded types
|
|
446
|
+
for i := 0; i < underlying.NumFields(); i++ {
|
|
447
|
+
field := underlying.Field(i)
|
|
448
|
+
fieldName := field.Name()
|
|
449
|
+
|
|
450
|
+
if !field.Exported() && field.Pkg() != c.pkg.Types {
|
|
451
|
+
continue
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if field.Anonymous() {
|
|
455
|
+
fieldType := field.Type()
|
|
456
|
+
isPtr := false
|
|
457
|
+
if ptr, ok := fieldType.(*types.Pointer); ok {
|
|
458
|
+
fieldType = ptr.Elem()
|
|
459
|
+
isPtr = true
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if named, ok := fieldType.(*types.Named); ok {
|
|
463
|
+
embeddedName := named.Obj().Name()
|
|
464
|
+
// For embedded structs, the init type should allow providing fields of the embedded struct,
|
|
465
|
+
// or the embedded struct itself.
|
|
466
|
+
// We generate Partial<EmbeddedStructFields> | EmbeddedStructType
|
|
467
|
+
// This is complex. For now, let's use ConstructorParameters as before for simplicity,
|
|
468
|
+
// or allow the embedded struct type itself.
|
|
469
|
+
// The example `Person?: ConstructorParameters<typeof Person>[0]` is for the fields.
|
|
470
|
+
// Let's try to generate a type that allows either the embedded struct instance or its fields.
|
|
471
|
+
// This might be: `Partial<FlattenedInitType<EmbeddedName>> | EmbeddedName`
|
|
472
|
+
// For now, stick to the simpler `ConstructorParameters<typeof %s>[0]` which implies field-based init.
|
|
473
|
+
// Or, more simply, the type of the embedded struct itself if it's a pointer.
|
|
474
|
+
if isPtr {
|
|
475
|
+
embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = c.getTypeString(field.Type()) // MyEmbeddedType | null
|
|
476
|
+
} else {
|
|
477
|
+
// For value-type embedded structs, allow initializing with its fields.
|
|
478
|
+
// This requires getting the flattened init type for the embedded struct.
|
|
479
|
+
// This could lead to recursion if not handled carefully.
|
|
480
|
+
// A simpler approach for now: use the embedded struct's own type.
|
|
481
|
+
// embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = c.getTypeString(field.Type())
|
|
482
|
+
// Or, using ConstructorParameters to allow field-based initialization:
|
|
483
|
+
embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = fmt.Sprintf("Partial<ConstructorParameters<typeof %s>[0]>", embeddedName)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
continue
|
|
487
|
+
}
|
|
488
|
+
fieldMap[fieldName] = c.getTypeString(field.Type())
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Promoted fields (handled by Go's embedding, init should use direct/embedded names)
|
|
492
|
+
// The current logic for `generateFlattenedInitTypeString` seems to focus on top-level
|
|
493
|
+
// settable properties in the constructor. Promoted fields are accessed via `this.promotedField`,
|
|
494
|
+
// not typically set directly in `init?` unless the embedded struct itself is named in `init?`.
|
|
495
|
+
|
|
496
|
+
// Add embedded types to the field map (these are the names of the embedded structs themselves)
|
|
497
|
+
for embeddedName, embeddedTSType := range embeddedTypeMap {
|
|
498
|
+
fieldMap[embeddedName] = embeddedTSType
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
var fieldNames []string
|
|
502
|
+
for name := range fieldMap {
|
|
503
|
+
fieldNames = append(fieldNames, name)
|
|
504
|
+
}
|
|
505
|
+
sort.Strings(fieldNames)
|
|
506
|
+
|
|
507
|
+
var fieldDefs []string
|
|
508
|
+
for _, fieldName := range fieldNames {
|
|
509
|
+
fieldDefs = append(fieldDefs, fmt.Sprintf("%s?: %s", fieldName, fieldMap[fieldName]))
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return "{" + strings.Join(fieldDefs, ", ") + "}"
|
|
513
|
+
}
|