goscript 0.0.22 → 0.0.24
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/README.md +1 -1
- package/cmd/goscript/cmd_compile.go +3 -3
- package/compiler/analysis.go +302 -182
- package/compiler/analysis_test.go +220 -0
- package/compiler/assignment.go +42 -43
- package/compiler/builtin_test.go +102 -0
- package/compiler/compiler.go +117 -29
- package/compiler/compiler_test.go +36 -8
- package/compiler/composite-lit.go +133 -53
- package/compiler/config.go +7 -3
- package/compiler/config_test.go +6 -33
- package/compiler/decl.go +36 -0
- package/compiler/expr-call.go +116 -60
- package/compiler/expr-selector.go +88 -43
- package/compiler/expr-star.go +57 -65
- package/compiler/expr-type.go +132 -5
- package/compiler/expr-value.go +8 -38
- package/compiler/expr.go +326 -30
- package/compiler/field.go +3 -3
- package/compiler/lit.go +34 -2
- package/compiler/primitive.go +19 -12
- package/compiler/spec-struct.go +140 -9
- package/compiler/spec-value.go +119 -41
- package/compiler/spec.go +21 -6
- package/compiler/stmt-assign.go +65 -3
- package/compiler/stmt-for.go +11 -0
- package/compiler/stmt-range.go +119 -11
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +175 -238
- package/compiler/type-assert.go +125 -379
- package/compiler/type.go +216 -129
- package/dist/gs/builtin/builtin.js +37 -0
- package/dist/gs/builtin/builtin.js.map +1 -0
- package/dist/gs/builtin/channel.js +471 -0
- package/dist/gs/builtin/channel.js.map +1 -0
- package/dist/gs/builtin/defer.js +54 -0
- package/dist/gs/builtin/defer.js.map +1 -0
- package/dist/gs/builtin/io.js +15 -0
- package/dist/gs/builtin/io.js.map +1 -0
- package/dist/gs/builtin/map.js +44 -0
- package/dist/gs/builtin/map.js.map +1 -0
- package/dist/gs/builtin/slice.js +799 -0
- package/dist/gs/builtin/slice.js.map +1 -0
- package/dist/gs/builtin/type.js +745 -0
- package/dist/gs/builtin/type.js.map +1 -0
- package/dist/gs/builtin/varRef.js +14 -0
- package/dist/gs/builtin/varRef.js.map +1 -0
- package/dist/gs/context/context.js +55 -0
- package/dist/gs/context/context.js.map +1 -0
- package/dist/gs/context/index.js +2 -0
- package/dist/gs/context/index.js.map +1 -0
- package/dist/gs/runtime/index.js +2 -0
- package/dist/gs/runtime/index.js.map +1 -0
- package/dist/gs/runtime/runtime.js +158 -0
- package/dist/gs/runtime/runtime.js.map +1 -0
- package/dist/gs/time/index.js +2 -0
- package/dist/gs/time/index.js.map +1 -0
- package/dist/gs/time/time.js +115 -0
- package/dist/gs/time/time.js.map +1 -0
- package/package.json +7 -6
- 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/config.go
CHANGED
|
@@ -13,13 +13,17 @@ type Config struct {
|
|
|
13
13
|
|
|
14
14
|
// Dir is the working directory for the compiler. If empty, uses the current working directory.
|
|
15
15
|
Dir string
|
|
16
|
-
//
|
|
17
|
-
|
|
16
|
+
// OutputPath is the output path root.
|
|
17
|
+
OutputPath string
|
|
18
18
|
// BuildFlags are the Go build flags (tags) to use during analysis.
|
|
19
19
|
BuildFlags []string
|
|
20
20
|
// AllDependencies controls whether to compile all dependencies of the requested packages.
|
|
21
21
|
// If true, all dependencies will be compiled; if false, only the requested packages are compiled.
|
|
22
22
|
AllDependencies bool
|
|
23
|
+
// DisableEmitBuiltin controls whether to emit builtin packages when they are referenced.
|
|
24
|
+
// If true, builtin packages will not be emitted; if false, they will be emitted if referenced.
|
|
25
|
+
// Default is false (emit builtin packages).
|
|
26
|
+
DisableEmitBuiltin bool
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
// Validate checks the config.
|
|
@@ -30,7 +34,7 @@ func (c *Config) Validate() error {
|
|
|
30
34
|
if c.fset == nil {
|
|
31
35
|
c.fset = token.NewFileSet()
|
|
32
36
|
}
|
|
33
|
-
if c.
|
|
37
|
+
if c.OutputPath == "" {
|
|
34
38
|
return errors.New("output path root must be specified")
|
|
35
39
|
}
|
|
36
40
|
return nil
|
package/compiler/config_test.go
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package compiler
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"reflect"
|
|
5
4
|
"testing"
|
|
6
5
|
)
|
|
7
6
|
|
|
@@ -15,9 +14,9 @@ func TestConfigValidate(t *testing.T) {
|
|
|
15
14
|
{
|
|
16
15
|
name: "valid config",
|
|
17
16
|
config: &Config{
|
|
18
|
-
Dir:
|
|
19
|
-
|
|
20
|
-
BuildFlags:
|
|
17
|
+
Dir: "/some/dir",
|
|
18
|
+
OutputPath: "/output/path",
|
|
19
|
+
BuildFlags: []string{"-tags", "sometag"},
|
|
21
20
|
},
|
|
22
21
|
wantErr: false,
|
|
23
22
|
},
|
|
@@ -33,9 +32,9 @@ func TestConfigValidate(t *testing.T) {
|
|
|
33
32
|
{
|
|
34
33
|
name: "nil fset gets initialized",
|
|
35
34
|
config: &Config{
|
|
36
|
-
fset:
|
|
37
|
-
Dir:
|
|
38
|
-
|
|
35
|
+
fset: nil,
|
|
36
|
+
Dir: "/some/dir",
|
|
37
|
+
OutputPath: "/output/path",
|
|
39
38
|
},
|
|
40
39
|
wantErr: false,
|
|
41
40
|
},
|
|
@@ -61,29 +60,3 @@ func TestConfigValidate(t *testing.T) {
|
|
|
61
60
|
})
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
|
-
|
|
65
|
-
func TestConfigFields(t *testing.T) {
|
|
66
|
-
// Verify that Config has the expected fields
|
|
67
|
-
config := Config{}
|
|
68
|
-
configType := reflect.TypeOf(config)
|
|
69
|
-
|
|
70
|
-
expectedFields := map[string]string{
|
|
71
|
-
"fset": "*token.FileSet",
|
|
72
|
-
"Dir": "string",
|
|
73
|
-
"OutputPathRoot": "string",
|
|
74
|
-
"BuildFlags": "[]string",
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
for fieldName, expectedType := range expectedFields {
|
|
78
|
-
field, exists := configType.FieldByName(fieldName)
|
|
79
|
-
if !exists {
|
|
80
|
-
t.Errorf("Expected Config to have field %s", fieldName)
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
actualType := field.Type.String()
|
|
85
|
-
if actualType != expectedType {
|
|
86
|
-
t.Errorf("Field %s has type %s, expected %s", fieldName, actualType, expectedType)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
package/compiler/decl.go
CHANGED
|
@@ -88,14 +88,50 @@ func (c *GoToTSCompiler) WriteFuncDeclAsFunction(decl *ast.FuncDecl) error {
|
|
|
88
88
|
return fmt.Errorf("failed to write function name: %w", err)
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// Write type parameters if present
|
|
92
|
+
if decl.Type.TypeParams != nil {
|
|
93
|
+
c.WriteTypeParameters(decl.Type.TypeParams)
|
|
94
|
+
}
|
|
95
|
+
|
|
91
96
|
// WriteFuncType needs to be aware if the function is async
|
|
92
97
|
c.WriteFuncType(decl.Type, isAsync) // Write signature (params, return type)
|
|
93
98
|
c.tsw.WriteLiterally(" ")
|
|
94
99
|
|
|
100
|
+
hasNamedReturns := false
|
|
101
|
+
if decl.Type.Results != nil {
|
|
102
|
+
for _, field := range decl.Type.Results.List {
|
|
103
|
+
if len(field.Names) > 0 {
|
|
104
|
+
hasNamedReturns = true
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if hasNamedReturns {
|
|
111
|
+
c.tsw.WriteLine("{")
|
|
112
|
+
c.tsw.Indent(1)
|
|
113
|
+
|
|
114
|
+
// Declare named return variables and initialize them to their zero values
|
|
115
|
+
for _, field := range decl.Type.Results.List {
|
|
116
|
+
for _, name := range field.Names {
|
|
117
|
+
c.tsw.WriteLiterallyf("let %s: ", name.Name)
|
|
118
|
+
c.WriteTypeExpr(field.Type)
|
|
119
|
+
c.tsw.WriteLiterally(" = ")
|
|
120
|
+
c.WriteZeroValueForType(c.pkg.TypesInfo.TypeOf(field.Type))
|
|
121
|
+
c.tsw.WriteLine("")
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
95
126
|
if err := c.WriteStmt(decl.Body); err != nil {
|
|
96
127
|
return fmt.Errorf("failed to write function body: %w", err)
|
|
97
128
|
}
|
|
98
129
|
|
|
130
|
+
if hasNamedReturns {
|
|
131
|
+
c.tsw.Indent(-1)
|
|
132
|
+
c.tsw.WriteLine("}")
|
|
133
|
+
}
|
|
134
|
+
|
|
99
135
|
return nil
|
|
100
136
|
}
|
|
101
137
|
|
package/compiler/expr-call.go
CHANGED
|
@@ -13,15 +13,19 @@ import (
|
|
|
13
13
|
// into its TypeScript equivalent.
|
|
14
14
|
// It handles several Go built-in functions specially:
|
|
15
15
|
// - `println(...)` becomes `console.log(...)`.
|
|
16
|
+
// - `panic(...)` becomes `$.panic(...)`.
|
|
16
17
|
// - `len(arg)` becomes `$.len(arg)`.
|
|
17
18
|
// - `cap(arg)` becomes `$.cap(arg)`.
|
|
18
19
|
// - `delete(m, k)` becomes `$.deleteMapEntry(m, k)`.
|
|
19
20
|
// - `make(chan T, size)` becomes `$.makeChannel<T_ts>(size, zeroValueForT)`.
|
|
20
21
|
// - `make(map[K]V)` becomes `$.makeMap<K_ts, V_ts>()`.
|
|
21
22
|
// - `make([]T, len, cap)` becomes `$.makeSlice<T_ts>(len, cap)`.
|
|
23
|
+
// - `make([]byte, len, cap)` becomes `new Uint8Array(len)`.
|
|
22
24
|
// - `string(runeVal)` becomes `String.fromCharCode(runeVal)`.
|
|
23
|
-
// - `string([]runeVal)`
|
|
24
|
-
// - `[]
|
|
25
|
+
// - `string([]runeVal)` becomes `$.runesToString(sliceVal)`.
|
|
26
|
+
// - `string([]byteVal)` becomes `$.bytesToString(sliceVal)`.
|
|
27
|
+
// - `[]rune(stringVal)` becomes `$.stringToRunes(stringVal)“.
|
|
28
|
+
// - `[]byte(stringVal)` becomes `$.stringToBytes(stringVal)`.
|
|
25
29
|
// - `close(ch)` becomes `ch.close()`.
|
|
26
30
|
// - `append(slice, elems...)` becomes `$.append(slice, elems...)`.
|
|
27
31
|
// - `byte(val)` becomes `$.byte(val)`.
|
|
@@ -70,66 +74,56 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
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
|
+
}
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
if funIdent, funIsIdent := expFun.(*ast.Ident); funIsIdent {
|
|
76
97
|
switch funIdent.String() {
|
|
98
|
+
case "panic":
|
|
99
|
+
c.tsw.WriteLiterally("$.panic")
|
|
77
100
|
case "println":
|
|
78
|
-
c.tsw.WriteLiterally("console.log
|
|
79
|
-
for i, arg := range exp.Args {
|
|
80
|
-
if i != 0 {
|
|
81
|
-
c.tsw.WriteLiterally(", ")
|
|
82
|
-
}
|
|
83
|
-
if err := c.WriteValueExpr(arg); err != nil {
|
|
84
|
-
return err
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
c.tsw.WriteLiterally(")")
|
|
88
|
-
return nil
|
|
101
|
+
c.tsw.WriteLiterally("console.log")
|
|
89
102
|
case "len":
|
|
90
103
|
// Translate len(arg) to $.len(arg)
|
|
91
|
-
if len(exp.Args)
|
|
92
|
-
|
|
93
|
-
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
94
|
-
return err
|
|
95
|
-
}
|
|
96
|
-
c.tsw.WriteLiterally(")")
|
|
97
|
-
return nil // Handled len
|
|
104
|
+
if len(exp.Args) != 1 {
|
|
105
|
+
return errors.Errorf("unhandled len call with incorrect number of arguments: %d != 1", len(exp.Args))
|
|
98
106
|
}
|
|
99
|
-
|
|
107
|
+
c.tsw.WriteLiterally("$.len")
|
|
100
108
|
case "cap":
|
|
101
109
|
// Translate cap(arg) to $.cap(arg)
|
|
102
|
-
if len(exp.Args)
|
|
103
|
-
|
|
104
|
-
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
105
|
-
return err
|
|
106
|
-
}
|
|
107
|
-
c.tsw.WriteLiterally(")")
|
|
108
|
-
return nil // Handled cap
|
|
110
|
+
if len(exp.Args) != 1 {
|
|
111
|
+
return errors.Errorf("unhandled cap call with incorrect number of arguments: %d != 1", len(exp.Args))
|
|
109
112
|
}
|
|
110
|
-
|
|
113
|
+
c.tsw.WriteLiterally("$.cap")
|
|
111
114
|
case "delete":
|
|
112
115
|
// Translate delete(map, key) to $.deleteMapEntry(map, key)
|
|
113
|
-
if len(exp.Args)
|
|
114
|
-
|
|
115
|
-
if err := c.WriteValueExpr(exp.Args[0]); err != nil { // Map
|
|
116
|
-
return err
|
|
117
|
-
}
|
|
118
|
-
c.tsw.WriteLiterally(", ")
|
|
119
|
-
if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Key
|
|
120
|
-
return err
|
|
121
|
-
}
|
|
122
|
-
c.tsw.WriteLiterally(")")
|
|
123
|
-
return nil // Handled delete
|
|
116
|
+
if len(exp.Args) != 2 {
|
|
117
|
+
return errors.Errorf("unhandled delete call with incorrect number of arguments: %d != 2", len(exp.Args))
|
|
124
118
|
}
|
|
125
|
-
|
|
119
|
+
c.tsw.WriteLiterally("$.deleteMapEntry")
|
|
126
120
|
case "make":
|
|
127
121
|
// First check if we have a channel type
|
|
128
122
|
if typ := c.pkg.TypesInfo.TypeOf(exp.Args[0]); typ != nil {
|
|
129
123
|
if chanType, ok := typ.Underlying().(*types.Chan); ok {
|
|
130
124
|
// Handle channel creation: make(chan T, bufferSize) or make(chan T)
|
|
131
125
|
c.tsw.WriteLiterally("$.makeChannel<")
|
|
132
|
-
c.WriteGoType(chanType.Elem())
|
|
126
|
+
c.WriteGoType(chanType.Elem(), GoTypeContextGeneral)
|
|
133
127
|
c.tsw.WriteLiterally(">(")
|
|
134
128
|
|
|
135
129
|
// If buffer size is provided, add it
|
|
@@ -189,13 +183,30 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
189
183
|
if sliceType == nil {
|
|
190
184
|
return errors.New("could not get type information for slice in make call")
|
|
191
185
|
}
|
|
192
|
-
|
|
186
|
+
goUnderlyingType, ok := sliceType.Underlying().(*types.Slice)
|
|
193
187
|
if !ok {
|
|
194
188
|
return errors.New("expected slice type for make call")
|
|
195
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
|
+
}
|
|
196
207
|
|
|
197
208
|
c.tsw.WriteLiterally("$.makeSlice<")
|
|
198
|
-
c.WriteGoType(goElemType
|
|
209
|
+
c.WriteGoType(goElemType, GoTypeContextGeneral) // Write the element type
|
|
199
210
|
c.tsw.WriteLiterally(">(")
|
|
200
211
|
|
|
201
212
|
if len(exp.Args) >= 2 {
|
|
@@ -253,7 +264,16 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
253
264
|
// Handle direct string(int32) conversion
|
|
254
265
|
// This assumes 'rune' is int32
|
|
255
266
|
if tv, ok := c.pkg.TypesInfo.Types[arg]; ok {
|
|
256
|
-
|
|
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) {
|
|
257
277
|
// Translate string(rune_val) to String.fromCharCode(rune_val)
|
|
258
278
|
c.tsw.WriteLiterally("String.fromCharCode(")
|
|
259
279
|
if err := c.WriteValueExpr(arg); err != nil {
|
|
@@ -266,18 +286,43 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
266
286
|
// Case 3: Argument is a slice of runes or bytes string([]rune{...}) or string([]byte{...})
|
|
267
287
|
if sliceType, isSlice := tv.Type.Underlying().(*types.Slice); isSlice {
|
|
268
288
|
if basic, isBasic := sliceType.Elem().Underlying().(*types.Basic); isBasic {
|
|
269
|
-
// Handle
|
|
270
|
-
if basic.Kind() == types.
|
|
271
|
-
|
|
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(...)
|
|
272
301
|
c.tsw.WriteLiterally("$.runesToString(")
|
|
273
302
|
if err := c.WriteValueExpr(arg); err != nil {
|
|
274
|
-
return fmt.Errorf("failed to write argument for string([]rune
|
|
303
|
+
return fmt.Errorf("failed to write argument for string([]rune) conversion: %w", err)
|
|
275
304
|
}
|
|
276
305
|
c.tsw.WriteLiterally(")")
|
|
277
|
-
return nil // Handled string([]rune)
|
|
306
|
+
return nil // Handled string([]rune)
|
|
278
307
|
}
|
|
279
308
|
}
|
|
280
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
|
+
}
|
|
281
326
|
}
|
|
282
327
|
}
|
|
283
328
|
// Return error for other unhandled string conversions
|
|
@@ -352,8 +397,10 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
352
397
|
return fmt.Errorf("failed to write argument for type cast: %w", err)
|
|
353
398
|
}
|
|
354
399
|
|
|
355
|
-
// Then use the TypeScript "as" operator with the type name
|
|
356
|
-
c.tsw.
|
|
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(")")
|
|
357
404
|
return nil // Handled non-function type cast
|
|
358
405
|
}
|
|
359
406
|
}
|
|
@@ -396,15 +443,24 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
396
443
|
return nil // Handled regular function call
|
|
397
444
|
}
|
|
398
445
|
} else {
|
|
399
|
-
//
|
|
400
|
-
if
|
|
401
|
-
|
|
402
|
-
|
|
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
|
+
}
|
|
403
458
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
+
}
|
|
408
464
|
}
|
|
409
465
|
}
|
|
410
466
|
}
|
|
@@ -13,16 +13,16 @@ import (
|
|
|
13
13
|
// access on an object or struct.
|
|
14
14
|
// - For package selectors, it writes `PackageName.IdentifierName`. The `IdentifierName`
|
|
15
15
|
// is written using `WriteIdent` which handles potential `.value` access if the
|
|
16
|
-
// package-level variable is
|
|
16
|
+
// package-level variable is varrefed.
|
|
17
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
|
|
18
|
+
// expression (`exp.X`) using `WriteValueExpr` (which handles its own varRefing).
|
|
19
19
|
// Then, it writes a dot (`.`) followed by the selected identifier (`exp.Sel`)
|
|
20
|
-
// using `WriteIdent`, which appends `.value` if the field itself is
|
|
20
|
+
// using `WriteIdent`, which appends `.value` if the field itself is varrefed
|
|
21
21
|
// (e.g., accessing a field of primitive type through a pointer to a struct
|
|
22
22
|
// where the field's address might have been taken).
|
|
23
23
|
//
|
|
24
24
|
// This function aims to correctly navigate Go's automatic dereferencing and
|
|
25
|
-
// TypeScript's explicit
|
|
25
|
+
// TypeScript's explicit varRefing model.
|
|
26
26
|
func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
|
|
27
27
|
// Check if this is a package selector (e.g., time.Now)
|
|
28
28
|
if pkgIdent, isPkgIdent := exp.X.(*ast.Ident); isPkgIdent {
|
|
@@ -31,75 +31,120 @@ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
|
|
|
31
31
|
// Package selectors should never use .value on the package name
|
|
32
32
|
c.tsw.WriteLiterally(pkgIdent.Name)
|
|
33
33
|
c.tsw.WriteLiterally(".")
|
|
34
|
-
// Write the selected identifier, allowing .value if it's a
|
|
34
|
+
// Write the selected identifier, allowing .value if it's a varrefed package variable
|
|
35
35
|
c.WriteIdent(exp.Sel, true)
|
|
36
36
|
return nil
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// --- Special case for dereferenced pointer to struct with field access: (*p).field ---
|
|
41
|
+
// --- Special case for dereferenced pointer to struct with field access: (*p).field or (**p).field etc ---
|
|
42
42
|
var baseExpr ast.Expr = exp.X
|
|
43
43
|
// Look inside parentheses if present
|
|
44
44
|
if parenExpr, isParen := exp.X.(*ast.ParenExpr); isParen {
|
|
45
45
|
baseExpr = parenExpr.X
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// Check if we have one or more star expressions (dereferences)
|
|
48
49
|
if starExpr, isStarExpr := baseExpr.(*ast.StarExpr); isStarExpr {
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
ptrObj = c.pkg.TypesInfo.ObjectOf(ptrIdent)
|
|
61
|
-
}
|
|
50
|
+
// Count the levels of dereference and find the innermost expression
|
|
51
|
+
dereferenceCount := 0
|
|
52
|
+
currentExpr := baseExpr
|
|
53
|
+
for {
|
|
54
|
+
if star, ok := currentExpr.(*ast.StarExpr); ok {
|
|
55
|
+
dereferenceCount++
|
|
56
|
+
currentExpr = star.X
|
|
57
|
+
} else {
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
}
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
// Get the type of the innermost expression (the pointer variable)
|
|
63
|
+
innerType := c.pkg.TypesInfo.TypeOf(currentExpr)
|
|
64
|
+
if innerType != nil {
|
|
65
|
+
// Check if after all dereferences we end up with a struct
|
|
66
|
+
finalType := innerType
|
|
67
|
+
for i := 0; i < dereferenceCount; i++ {
|
|
68
|
+
if ptrType, ok := finalType.(*types.Pointer); ok {
|
|
69
|
+
finalType = ptrType.Elem()
|
|
70
|
+
} else {
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
}
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
// If the final type is a struct, handle field access specially
|
|
76
|
+
if _, isStruct := finalType.Underlying().(*types.Struct); isStruct {
|
|
77
|
+
// Write the fully dereferenced expression
|
|
78
|
+
if err := c.WriteValueExpr(starExpr); err != nil {
|
|
79
|
+
return fmt.Errorf("failed to write dereferenced expression for field access: %w", err)
|
|
80
|
+
}
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
// Check if we need an extra .value for varrefed struct access
|
|
83
|
+
// This happens when the struct being pointed to is varrefed
|
|
84
|
+
needsExtraValue := false
|
|
85
|
+
if ident, ok := currentExpr.(*ast.Ident); ok {
|
|
86
|
+
if obj := c.pkg.TypesInfo.ObjectOf(ident); obj != nil {
|
|
87
|
+
// Check if after dereferencing, we get a varrefed struct
|
|
88
|
+
ptrType := obj.Type()
|
|
89
|
+
for i := 0; i < dereferenceCount; i++ {
|
|
90
|
+
if ptr, ok := ptrType.(*types.Pointer); ok {
|
|
91
|
+
ptrType = ptr.Elem()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// If the final pointed-to type suggests the struct is varrefed
|
|
95
|
+
// (i.e., the dereference operation results in VarRef<Struct>)
|
|
96
|
+
if c.analysis.NeedsVarRefAccess(obj) {
|
|
97
|
+
needsExtraValue = true
|
|
75
98
|
}
|
|
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
99
|
}
|
|
82
100
|
}
|
|
101
|
+
|
|
102
|
+
if needsExtraValue {
|
|
103
|
+
c.tsw.WriteLiterally("!.value")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add .field
|
|
107
|
+
c.tsw.WriteLiterally(".")
|
|
108
|
+
c.WriteIdent(exp.Sel, false) // Don't add .value to the field itself
|
|
109
|
+
return nil
|
|
83
110
|
}
|
|
84
111
|
}
|
|
85
112
|
}
|
|
86
113
|
// --- End Special Case ---
|
|
87
114
|
|
|
88
115
|
// Fallback / Normal Case (e.g., obj.Field, pkg.Var, method calls)
|
|
89
|
-
// WriteValueExpr handles adding .value for the base variable itself if it's
|
|
116
|
+
// WriteValueExpr handles adding .value for the base variable itself if it's varrefed.
|
|
90
117
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
91
118
|
return fmt.Errorf("failed to write selector base expression: %w", err)
|
|
92
119
|
}
|
|
93
120
|
|
|
94
|
-
// Add
|
|
95
|
-
|
|
121
|
+
// Add null assertion for selector expressions when accessing fields/methods on nullable types
|
|
122
|
+
// In Go, accessing fields or calling methods on nil pointers/interfaces panics, so we should throw in TypeScript
|
|
123
|
+
baseType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
124
|
+
if baseType != nil {
|
|
125
|
+
// Check if the base is a pointer type
|
|
126
|
+
if _, isPtr := baseType.(*types.Pointer); isPtr {
|
|
127
|
+
c.tsw.WriteLiterally("!.")
|
|
128
|
+
} else if _, isInterface := baseType.Underlying().(*types.Interface); isInterface {
|
|
129
|
+
// For interface types, add null assertion since interfaces can be nil
|
|
130
|
+
c.tsw.WriteLiterally("!.")
|
|
131
|
+
} else if callExpr, isCall := exp.X.(*ast.CallExpr); isCall {
|
|
132
|
+
// For function calls that return nullable types, add null assertion
|
|
133
|
+
_ = callExpr // Use the variable to avoid unused error
|
|
134
|
+
c.tsw.WriteLiterally("!.")
|
|
135
|
+
} else {
|
|
136
|
+
// Add .
|
|
137
|
+
c.tsw.WriteLiterally(".")
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// Add .
|
|
141
|
+
c.tsw.WriteLiterally(".")
|
|
142
|
+
}
|
|
96
143
|
|
|
97
144
|
// Write the field/method name.
|
|
98
|
-
// Pass '
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
// relies on NeedsBoxedAccess for the field 'Val', which should typically be false.
|
|
103
|
-
c.WriteIdent(exp.Sel, true)
|
|
145
|
+
// Pass 'false' to WriteIdent to NOT add '.value' for struct fields.
|
|
146
|
+
// Struct fields use getters/setters, so we don't want to add .value here.
|
|
147
|
+
// The setter will handle the internal .value access.
|
|
148
|
+
c.WriteIdent(exp.Sel, false)
|
|
104
149
|
return nil
|
|
105
150
|
}
|