goscript 0.0.61 → 0.0.63
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 +62 -46
- package/compiler/analysis.go +621 -19
- package/compiler/analysis_test.go +3 -3
- package/compiler/assignment.go +100 -0
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +76 -16
- package/compiler/compiler_test.go +9 -9
- package/compiler/composite-lit.go +29 -8
- package/compiler/decl.go +20 -11
- package/compiler/expr-call-async.go +26 -1
- package/compiler/expr-call-builtins.go +60 -4
- package/compiler/expr-call-type-conversion.go +37 -5
- package/compiler/expr-call.go +26 -6
- package/compiler/expr-selector.go +35 -2
- package/compiler/expr-type.go +12 -2
- package/compiler/expr.go +61 -0
- package/compiler/index.test.ts +3 -1
- package/compiler/lit.go +13 -4
- package/compiler/spec-struct.go +30 -8
- package/compiler/spec-value.go +2 -2
- package/compiler/spec.go +23 -4
- package/compiler/stmt-assign.go +124 -0
- package/compiler/stmt-range.go +2 -2
- package/compiler/stmt.go +160 -14
- package/compiler/type-info.go +3 -5
- package/compiler/type-utils.go +40 -1
- package/compiler/type.go +52 -14
- package/dist/gs/builtin/builtin.d.ts +8 -1
- package/dist/gs/builtin/builtin.js +26 -1
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/errors.d.ts +1 -0
- package/dist/gs/builtin/errors.js +8 -0
- package/dist/gs/builtin/errors.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +5 -4
- package/dist/gs/builtin/slice.js +88 -51
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +23 -2
- package/dist/gs/builtin/type.js +125 -0
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/builtin/varRef.d.ts +3 -0
- package/dist/gs/builtin/varRef.js +6 -1
- package/dist/gs/builtin/varRef.js.map +1 -1
- package/dist/gs/bytes/reader.gs.d.ts +1 -1
- package/dist/gs/bytes/reader.gs.js +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +2 -2
- package/dist/gs/reflect/index.js +1 -1
- package/dist/gs/reflect/index.js.map +1 -1
- package/dist/gs/reflect/map.d.ts +3 -2
- package/dist/gs/reflect/map.js +37 -3
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +53 -12
- package/dist/gs/reflect/type.js +906 -31
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.d.ts +11 -12
- package/dist/gs/reflect/types.js +26 -15
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/value.d.ts +4 -4
- package/dist/gs/reflect/value.js +8 -2
- package/dist/gs/reflect/value.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +21 -0
- package/dist/gs/slices/slices.js +48 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.js +20 -2
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +3 -3
- package/dist/gs/sync/atomic/type.gs.js +13 -7
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
- package/dist/gs/unicode/utf8/utf8.js +10 -6
- package/dist/gs/unicode/utf8/utf8.js.map +1 -1
- package/go.mod +6 -6
- package/go.sum +12 -8
- package/gs/builtin/builtin.ts +27 -2
- package/gs/builtin/errors.ts +12 -0
- package/gs/builtin/slice.ts +126 -55
- package/gs/builtin/type.ts +159 -2
- package/gs/builtin/varRef.ts +8 -2
- package/gs/bytes/reader.gs.ts +2 -2
- package/gs/math/hypot.gs.test.ts +3 -1
- package/gs/math/pow10.gs.test.ts +5 -4
- package/gs/reflect/index.ts +3 -2
- package/gs/reflect/map.test.ts +7 -6
- package/gs/reflect/map.ts +49 -7
- package/gs/reflect/type.ts +1150 -57
- package/gs/reflect/types.ts +34 -21
- package/gs/reflect/value.ts +12 -6
- package/gs/slices/slices.ts +55 -0
- package/gs/strconv/atoi.gs.ts +18 -2
- package/gs/sync/atomic/type.gs.ts +15 -10
- package/gs/unicode/utf8/utf8.ts +12 -8
- package/package.json +23 -14
|
@@ -25,6 +25,9 @@ var builtinFunctions = map[string]bool{
|
|
|
25
25
|
"recover": true,
|
|
26
26
|
"print": true,
|
|
27
27
|
"println": true,
|
|
28
|
+
"min": true,
|
|
29
|
+
"max": true,
|
|
30
|
+
"clear": true,
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
// writeBuiltinFunction handles built-in Go functions
|
|
@@ -34,7 +37,7 @@ func (c *GoToTSCompiler) writeBuiltinFunction(exp *ast.CallExpr, funName string)
|
|
|
34
37
|
c.tsw.WriteLiterally("$.panic")
|
|
35
38
|
return true, nil
|
|
36
39
|
case "println":
|
|
37
|
-
c.tsw.WriteLiterally("
|
|
40
|
+
c.tsw.WriteLiterally("$.println")
|
|
38
41
|
return true, nil
|
|
39
42
|
case "len":
|
|
40
43
|
if len(exp.Args) != 1 {
|
|
@@ -89,6 +92,46 @@ func (c *GoToTSCompiler) writeBuiltinFunction(exp *ast.CallExpr, funName string)
|
|
|
89
92
|
return true, nil
|
|
90
93
|
case "append":
|
|
91
94
|
return true, c.writeAppendCall(exp)
|
|
95
|
+
case "min":
|
|
96
|
+
if len(exp.Args) < 1 {
|
|
97
|
+
return true, errors.New("unhandled min call with no arguments")
|
|
98
|
+
}
|
|
99
|
+
c.tsw.WriteLiterally("Math.min(")
|
|
100
|
+
for i, arg := range exp.Args {
|
|
101
|
+
if i > 0 {
|
|
102
|
+
c.tsw.WriteLiterally(", ")
|
|
103
|
+
}
|
|
104
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
105
|
+
return true, fmt.Errorf("failed to write argument %d in min call: %w", i, err)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
c.tsw.WriteLiterally(")")
|
|
109
|
+
return true, nil
|
|
110
|
+
case "max":
|
|
111
|
+
if len(exp.Args) < 1 {
|
|
112
|
+
return true, errors.New("unhandled max call with no arguments")
|
|
113
|
+
}
|
|
114
|
+
c.tsw.WriteLiterally("Math.max(")
|
|
115
|
+
for i, arg := range exp.Args {
|
|
116
|
+
if i > 0 {
|
|
117
|
+
c.tsw.WriteLiterally(", ")
|
|
118
|
+
}
|
|
119
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
120
|
+
return true, fmt.Errorf("failed to write argument %d in max call: %w", i, err)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
c.tsw.WriteLiterally(")")
|
|
124
|
+
return true, nil
|
|
125
|
+
case "clear":
|
|
126
|
+
if len(exp.Args) != 1 {
|
|
127
|
+
return true, errors.Errorf("unhandled clear call with incorrect number of arguments: %d != 1", len(exp.Args))
|
|
128
|
+
}
|
|
129
|
+
c.tsw.WriteLiterally("$.clear(")
|
|
130
|
+
if err := c.WriteValueExpr(exp.Args[0]); err != nil {
|
|
131
|
+
return true, fmt.Errorf("failed to write argument in clear call: %w", err)
|
|
132
|
+
}
|
|
133
|
+
c.tsw.WriteLiterally(")")
|
|
134
|
+
return true, nil
|
|
92
135
|
case "byte":
|
|
93
136
|
if len(exp.Args) != 1 {
|
|
94
137
|
return true, errors.Errorf("unhandled byte call with incorrect number of arguments: %d != 1", len(exp.Args))
|
|
@@ -119,13 +162,16 @@ func (c *GoToTSCompiler) writeAppendCall(exp *ast.CallExpr) error {
|
|
|
119
162
|
}
|
|
120
163
|
|
|
121
164
|
// The remaining arguments are the elements to append
|
|
122
|
-
|
|
165
|
+
elemsToAppend := exp.Args[1:]
|
|
166
|
+
for i, arg := range elemsToAppend {
|
|
123
167
|
if i > 0 || len(exp.Args) > 1 {
|
|
124
168
|
c.tsw.WriteLiterally(", ")
|
|
125
169
|
}
|
|
126
170
|
|
|
127
|
-
//
|
|
128
|
-
|
|
171
|
+
// Handle ellipsis (spread) for the last argument: append(slice, elems...)
|
|
172
|
+
// The ellipsis can only appear on the last argument, so check if this is the last element
|
|
173
|
+
isLastElement := i == len(elemsToAppend)-1
|
|
174
|
+
if exp.Ellipsis != token.NoPos && isLastElement {
|
|
129
175
|
// Check if the slice is []byte and the argument is a string
|
|
130
176
|
sliceType := c.pkg.TypesInfo.TypeOf(exp.Args[0])
|
|
131
177
|
argType := c.pkg.TypesInfo.TypeOf(arg)
|
|
@@ -141,6 +187,16 @@ func (c *GoToTSCompiler) writeAppendCall(exp *ast.CallExpr) error {
|
|
|
141
187
|
continue
|
|
142
188
|
}
|
|
143
189
|
}
|
|
190
|
+
|
|
191
|
+
// For other slice types with ellipsis, use spread operator
|
|
192
|
+
// append(slice, anotherSlice...) -> $.append(slice, ...(anotherSlice || []))
|
|
193
|
+
// The || [] handles the case where anotherSlice is null (nil in Go)
|
|
194
|
+
c.tsw.WriteLiterally("...(")
|
|
195
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
196
|
+
return fmt.Errorf("failed to write spread argument in append call: %w", err)
|
|
197
|
+
}
|
|
198
|
+
c.tsw.WriteLiterally(" || [])")
|
|
199
|
+
continue
|
|
144
200
|
}
|
|
145
201
|
|
|
146
202
|
if err := c.WriteValueExpr(arg); err != nil {
|
|
@@ -18,14 +18,33 @@ func (c *GoToTSCompiler) writeNilConversion(exp *ast.CallExpr) (handled bool, er
|
|
|
18
18
|
return false, nil
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
// Check if this is actually a type conversion, not a method/function call
|
|
22
|
+
// For type conversions, exp.Fun is a type expression (IsType() is true)
|
|
23
|
+
// For method calls like s.ptr.Swap(nil), exp.Fun is a selector expression (IsType() is false)
|
|
24
|
+
if tv, ok := c.pkg.TypesInfo.Types[exp.Fun]; !ok || !tv.IsType() {
|
|
25
|
+
// This is not a type conversion, let the normal call handling proceed
|
|
26
|
+
return false, nil
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get the type being converted to
|
|
30
|
+
if typ := c.pkg.TypesInfo.TypeOf(exp.Fun); typ != nil {
|
|
31
|
+
// For pointer types, create a typed nil that preserves type information
|
|
32
|
+
if ptrType, ok := typ.(*types.Pointer); ok {
|
|
33
|
+
// Use a qualifier that returns the package name for local types
|
|
34
|
+
// This matches Go's reflect output format (e.g., "main.Stringer")
|
|
35
|
+
qualifier := func(pkg *types.Package) string {
|
|
36
|
+
if pkg == nil {
|
|
37
|
+
return ""
|
|
38
|
+
}
|
|
39
|
+
return pkg.Name()
|
|
40
|
+
}
|
|
41
|
+
typeName := types.TypeString(ptrType, qualifier)
|
|
42
|
+
c.tsw.WriteLiterallyf("$.typedNil(%q)", typeName)
|
|
25
43
|
return true, nil
|
|
26
44
|
}
|
|
27
45
|
}
|
|
28
46
|
|
|
47
|
+
// For non-pointer types (or if type info is unavailable), use plain null
|
|
29
48
|
c.tsw.WriteLiterally("null")
|
|
30
49
|
return true, nil
|
|
31
50
|
}
|
|
@@ -141,8 +160,21 @@ func (c *GoToTSCompiler) writeStringConversion(exp *ast.CallExpr) error {
|
|
|
141
160
|
|
|
142
161
|
// Handle direct string(int32) conversion
|
|
143
162
|
if tv, ok := c.pkg.TypesInfo.Types[arg]; ok {
|
|
144
|
-
// Case 3a: Argument is already a string - no-op
|
|
163
|
+
// Case 3a: Argument is already a string - no-op (unless it's a named type with toString)
|
|
145
164
|
if c.isStringType(tv.Type) {
|
|
165
|
+
// Check if this is a named type from the reflect package (like StructTag)
|
|
166
|
+
// which is implemented as a class in TypeScript with a toString() method
|
|
167
|
+
if namedType, isNamed := tv.Type.(*types.Named); isNamed {
|
|
168
|
+
obj := namedType.Obj()
|
|
169
|
+
if obj != nil && obj.Pkg() != nil && obj.Pkg().Path() == "reflect" && obj.Name() == "StructTag" {
|
|
170
|
+
// Call toString() for reflect.StructTag
|
|
171
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
172
|
+
return fmt.Errorf("failed to write argument for string(reflect.StructTag) conversion: %w", err)
|
|
173
|
+
}
|
|
174
|
+
c.tsw.WriteLiterally(".toString()")
|
|
175
|
+
return nil
|
|
176
|
+
}
|
|
177
|
+
}
|
|
146
178
|
// Translate string(stringValue) to stringValue (no-op)
|
|
147
179
|
if err := c.WriteValueExpr(arg); err != nil {
|
|
148
180
|
return fmt.Errorf("failed to write argument for string(string) no-op conversion: %w", err)
|
package/compiler/expr-call.go
CHANGED
|
@@ -61,7 +61,8 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
61
61
|
// For built-ins that don't return early, write the arguments
|
|
62
62
|
if funIdent.String() != "new" && funIdent.String() != "close" && funIdent.String() != "make" &&
|
|
63
63
|
funIdent.String() != "string" && funIdent.String() != "append" && funIdent.String() != "byte" &&
|
|
64
|
-
funIdent.String() != "int"
|
|
64
|
+
funIdent.String() != "int" && funIdent.String() != "min" && funIdent.String() != "max" &&
|
|
65
|
+
funIdent.String() != "clear" {
|
|
65
66
|
return c.writeCallArguments(exp)
|
|
66
67
|
}
|
|
67
68
|
return nil
|
|
@@ -117,14 +118,26 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
117
118
|
}
|
|
118
119
|
c.tsw.WriteLiterally(")")
|
|
119
120
|
} else {
|
|
121
|
+
// Check if this is a function call that returns a function (e.g., simpleIterator(m)())
|
|
122
|
+
// and if the inner call is async, wrap it in parentheses
|
|
123
|
+
innerCallExpr, isCallExpr := expFun.(*ast.CallExpr)
|
|
124
|
+
needsParens := isCallExpr && c.isCallExprAsync(innerCallExpr)
|
|
125
|
+
|
|
126
|
+
if needsParens {
|
|
127
|
+
c.tsw.WriteLiterally("(")
|
|
128
|
+
}
|
|
129
|
+
|
|
120
130
|
// Not an identifier (e.g., method call on a value, function call result)
|
|
121
131
|
if err := c.WriteValueExpr(expFun); err != nil {
|
|
122
132
|
return fmt.Errorf("failed to write method expression in call: %w", err)
|
|
123
133
|
}
|
|
124
134
|
|
|
125
|
-
|
|
135
|
+
if needsParens {
|
|
136
|
+
c.tsw.WriteLiterally(")")
|
|
137
|
+
}
|
|
138
|
+
|
|
126
139
|
// Add non-null assertion since the returned function could be null
|
|
127
|
-
if
|
|
140
|
+
if isCallExpr {
|
|
128
141
|
c.tsw.WriteLiterally("!")
|
|
129
142
|
} else {
|
|
130
143
|
c.addNonNullAssertion(expFun)
|
|
@@ -210,8 +223,8 @@ func (c *GoToTSCompiler) writeArgumentWithTypeHandling(arg ast.Expr, funcSig *ty
|
|
|
210
223
|
return nil
|
|
211
224
|
}
|
|
212
225
|
|
|
213
|
-
// resolveImportAlias returns the import alias for a given package
|
|
214
|
-
// This is the single source of truth for import alias resolution
|
|
226
|
+
// resolveImportAlias returns the import alias for a given package.
|
|
227
|
+
// This is the single source of truth for import alias resolution.
|
|
215
228
|
func (c *GoToTSCompiler) resolveImportAlias(pkg *types.Package) (alias string, found bool) {
|
|
216
229
|
if c.analysis == nil || pkg == nil {
|
|
217
230
|
return "", false
|
|
@@ -219,11 +232,18 @@ func (c *GoToTSCompiler) resolveImportAlias(pkg *types.Package) (alias string, f
|
|
|
219
232
|
|
|
220
233
|
pkgName := pkg.Name()
|
|
221
234
|
|
|
222
|
-
// Try to match by the actual package name
|
|
235
|
+
// Try to match by the actual package name in regular imports
|
|
223
236
|
if _, exists := c.analysis.Imports[pkgName]; exists {
|
|
224
237
|
return pkgName, true
|
|
225
238
|
}
|
|
226
239
|
|
|
240
|
+
// Also check synthetic imports for the current file
|
|
241
|
+
if syntheticImports := c.analysis.SyntheticImportsPerFile[c.currentFilePath]; syntheticImports != nil {
|
|
242
|
+
if _, exists := syntheticImports[pkgName]; exists {
|
|
243
|
+
return pkgName, true
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
227
247
|
return "", false
|
|
228
248
|
}
|
|
229
249
|
|
|
@@ -130,10 +130,23 @@ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
|
|
|
130
130
|
|
|
131
131
|
// Fallback / Normal Case (e.g., obj.Field, pkg.Var, method calls)
|
|
132
132
|
// WriteValueExpr handles adding .value for the base variable itself if it's varrefed.
|
|
133
|
+
|
|
134
|
+
// Check if the base expression is an async call - if so, we need to wrap it in parentheses
|
|
135
|
+
// so that await binds correctly: (await asyncFn()).method instead of await asyncFn().method
|
|
136
|
+
needsParensForAsync := false
|
|
137
|
+
if callExpr, isCall := exp.X.(*ast.CallExpr); isCall && c.isCallExprAsync(callExpr) {
|
|
138
|
+
needsParensForAsync = true
|
|
139
|
+
c.tsw.WriteLiterally("(")
|
|
140
|
+
}
|
|
141
|
+
|
|
133
142
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
134
143
|
return fmt.Errorf("failed to write selector base expression: %w", err)
|
|
135
144
|
}
|
|
136
145
|
|
|
146
|
+
if needsParensForAsync {
|
|
147
|
+
c.tsw.WriteLiterally(")")
|
|
148
|
+
}
|
|
149
|
+
|
|
137
150
|
// Add null assertion for selector expressions when accessing fields/methods on nullable types
|
|
138
151
|
// In Go, accessing fields or calling methods on nil pointers/interfaces panics, so we should throw in TypeScript
|
|
139
152
|
baseType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
@@ -212,8 +225,24 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
|
|
|
212
225
|
return fmt.Errorf("failed to get qualified type name for primitive receiver")
|
|
213
226
|
}
|
|
214
227
|
|
|
215
|
-
//
|
|
216
|
-
|
|
228
|
+
// Get the method parameters to forward them
|
|
229
|
+
params := sig.Params()
|
|
230
|
+
paramNames := make([]string, params.Len())
|
|
231
|
+
for i := 0; i < params.Len(); i++ {
|
|
232
|
+
paramNames[i] = fmt.Sprintf("_p%d", i)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Generate: ((_p0: type0, _p1: type1, ...) => TypeName_MethodName(receiverValue, _p0, _p1, ...))
|
|
236
|
+
c.tsw.WriteLiterally("((")
|
|
237
|
+
for i, name := range paramNames {
|
|
238
|
+
if i > 0 {
|
|
239
|
+
c.tsw.WriteLiterally(", ")
|
|
240
|
+
}
|
|
241
|
+
c.tsw.WriteLiterally(name)
|
|
242
|
+
c.tsw.WriteLiterally(": ")
|
|
243
|
+
c.WriteGoType(params.At(i).Type(), GoTypeContextGeneral)
|
|
244
|
+
}
|
|
245
|
+
c.tsw.WriteLiterally(") => ")
|
|
217
246
|
c.tsw.WriteLiterally(typeName)
|
|
218
247
|
c.tsw.WriteLiterally("_")
|
|
219
248
|
c.tsw.WriteLiterally(exp.Sel.Name)
|
|
@@ -221,6 +250,10 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
|
|
|
221
250
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
222
251
|
return fmt.Errorf("failed to write method value receiver: %w", err)
|
|
223
252
|
}
|
|
253
|
+
for _, name := range paramNames {
|
|
254
|
+
c.tsw.WriteLiterally(", ")
|
|
255
|
+
c.tsw.WriteLiterally(name)
|
|
256
|
+
}
|
|
224
257
|
c.tsw.WriteLiterally("))")
|
|
225
258
|
|
|
226
259
|
return nil
|
package/compiler/expr-type.go
CHANGED
|
@@ -26,13 +26,23 @@ func (c *GoToTSCompiler) WriteTypeExpr(a ast.Expr) {
|
|
|
26
26
|
// Check if this is a package selector (e.g., os.FileInfo)
|
|
27
27
|
if obj := c.pkg.TypesInfo.Uses[pkgIdent]; obj != nil {
|
|
28
28
|
if _, isPkg := obj.(*types.PkgName); isPkg {
|
|
29
|
-
// This is a package.Type reference
|
|
29
|
+
// This is a package.Type reference
|
|
30
|
+
typ := c.pkg.TypesInfo.TypeOf(a)
|
|
31
|
+
|
|
32
|
+
// Check if this is an interface type and add null | prefix
|
|
33
|
+
if typ != nil {
|
|
34
|
+
if _, isInterface := typ.Underlying().(*types.Interface); isInterface {
|
|
35
|
+
c.tsw.WriteLiterally("null | ")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Write the qualified name
|
|
30
40
|
c.tsw.WriteLiterally(pkgIdent.Name)
|
|
31
41
|
c.tsw.WriteLiterally(".")
|
|
32
42
|
c.tsw.WriteLiterally(selectorExpr.Sel.Name)
|
|
33
43
|
|
|
34
44
|
// Check if this is a function type and add | null
|
|
35
|
-
if typ
|
|
45
|
+
if typ != nil {
|
|
36
46
|
if namedType, isNamed := typ.(*types.Named); isNamed {
|
|
37
47
|
if _, isSignature := namedType.Underlying().(*types.Signature); isSignature {
|
|
38
48
|
c.tsw.WriteLiterally(" | null")
|
package/compiler/expr.go
CHANGED
|
@@ -441,9 +441,66 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
441
441
|
c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
// Check if this is a comparison that might trigger TypeScript's control flow narrowing.
|
|
445
|
+
// When a numeric field is compared to a literal, TypeScript may narrow the field type
|
|
446
|
+
// to that literal, causing subsequent comparisons to appear unintentional.
|
|
447
|
+
// We cast to number to widen the type and avoid these false positives.
|
|
448
|
+
needsNumberCast := false
|
|
449
|
+
if (exp.Op == token.EQL || exp.Op == token.NEQ) && c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
450
|
+
// Check if left side is a selector expression (field access) and right is a literal or constant
|
|
451
|
+
_, leftIsSelector := exp.X.(*ast.SelectorExpr)
|
|
452
|
+
_, rightIsLiteral := exp.Y.(*ast.BasicLit)
|
|
453
|
+
// Also check if right side is an identifier referring to a constant
|
|
454
|
+
rightIsConstant := false
|
|
455
|
+
if rightIdent, ok := exp.Y.(*ast.Ident); ok {
|
|
456
|
+
if obj := c.pkg.TypesInfo.Uses[rightIdent]; obj != nil {
|
|
457
|
+
if _, isConst := obj.(*types.Const); isConst {
|
|
458
|
+
rightIsConstant = true
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if leftIsSelector && (rightIsLiteral || rightIsConstant) {
|
|
463
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
464
|
+
if leftType != nil {
|
|
465
|
+
if basic, ok := leftType.Underlying().(*types.Basic); ok {
|
|
466
|
+
// Check if it's a numeric type
|
|
467
|
+
if basic.Info()&types.IsNumeric != 0 {
|
|
468
|
+
needsNumberCast = true
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Check if this is integer division (Go's / operator truncates for integers)
|
|
476
|
+
isIntegerDivision := false
|
|
477
|
+
if exp.Op == token.QUO && c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
478
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
479
|
+
rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
|
|
480
|
+
if leftType != nil && rightType != nil {
|
|
481
|
+
leftBasic, leftIsBasic := leftType.Underlying().(*types.Basic)
|
|
482
|
+
rightBasic, rightIsBasic := rightType.Underlying().(*types.Basic)
|
|
483
|
+
if leftIsBasic && rightIsBasic {
|
|
484
|
+
// Check if both are integer types (not floating point)
|
|
485
|
+
isIntegerDivision = (leftBasic.Info()&types.IsInteger != 0) && (rightBasic.Info()&types.IsInteger != 0)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if isIntegerDivision {
|
|
491
|
+
// Wrap integer division in Math.trunc() to match Go's integer division behavior
|
|
492
|
+
c.tsw.WriteLiterally("Math.trunc(")
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if needsNumberCast {
|
|
496
|
+
c.tsw.WriteLiterally("Number(")
|
|
497
|
+
}
|
|
444
498
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
445
499
|
return fmt.Errorf("failed to write binary expression left operand: %w", err)
|
|
446
500
|
}
|
|
501
|
+
if needsNumberCast {
|
|
502
|
+
c.tsw.WriteLiterally(")")
|
|
503
|
+
}
|
|
447
504
|
|
|
448
505
|
c.tsw.WriteLiterally(" ")
|
|
449
506
|
tokStr, ok := TokenToTs(exp.Op)
|
|
@@ -458,6 +515,10 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
458
515
|
return fmt.Errorf("failed to write binary expression right operand: %w", err)
|
|
459
516
|
}
|
|
460
517
|
|
|
518
|
+
if isIntegerDivision {
|
|
519
|
+
c.tsw.WriteLiterally(")") // Close Math.trunc()
|
|
520
|
+
}
|
|
521
|
+
|
|
461
522
|
if isBitwise {
|
|
462
523
|
c.tsw.WriteLiterally(")") // Add closing parenthesis for bitwise operations
|
|
463
524
|
}
|
package/compiler/index.test.ts
CHANGED
|
@@ -24,6 +24,8 @@ describe("GoScript Compiler API", () => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
await expect(compile(config)).resolves.toBeUndefined();
|
|
27
|
-
|
|
27
|
+
// fs.access resolves to undefined/null on success - verify file exists
|
|
28
|
+
const fileExists = await fs.access(expectedOutputFile).then(() => true).catch(() => false);
|
|
29
|
+
expect(fileExists).toBe(true);
|
|
28
30
|
}, 30000); // 30 second timeout for compilation
|
|
29
31
|
});
|
package/compiler/lit.go
CHANGED
|
@@ -143,11 +143,20 @@ func (c *GoToTSCompiler) WriteFuncLitValue(exp *ast.FuncLit) error {
|
|
|
143
143
|
c.WriteTypeExpr(exp.Type.Results.List[0].Type)
|
|
144
144
|
} else {
|
|
145
145
|
c.tsw.WriteLiterally("[")
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
first := true
|
|
147
|
+
for _, field := range exp.Type.Results.List {
|
|
148
|
+
// Each field may represent multiple return values (e.g., "a, b int")
|
|
149
|
+
count := len(field.Names)
|
|
150
|
+
if count == 0 {
|
|
151
|
+
count = 1 // Unnamed return value
|
|
152
|
+
}
|
|
153
|
+
for j := 0; j < count; j++ {
|
|
154
|
+
if !first {
|
|
155
|
+
c.tsw.WriteLiterally(", ")
|
|
156
|
+
}
|
|
157
|
+
first = false
|
|
158
|
+
c.WriteTypeExpr(field.Type)
|
|
149
159
|
}
|
|
150
|
-
c.WriteTypeExpr(field.Type)
|
|
151
160
|
}
|
|
152
161
|
c.tsw.WriteLiterally("]")
|
|
153
162
|
}
|
package/compiler/spec-struct.go
CHANGED
|
@@ -4,7 +4,7 @@ import (
|
|
|
4
4
|
"fmt"
|
|
5
5
|
"go/ast"
|
|
6
6
|
"go/types"
|
|
7
|
-
"
|
|
7
|
+
"slices"
|
|
8
8
|
"strings"
|
|
9
9
|
)
|
|
10
10
|
|
|
@@ -109,8 +109,10 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Use AST-based type string when available, fall back to types-based
|
|
112
|
+
// Note: getASTTypeString already includes "null |" for interface types
|
|
112
113
|
astType := fieldASTTypes[fieldKeyName]
|
|
113
114
|
fieldTsType := c.getASTTypeString(astType, field.Type())
|
|
115
|
+
|
|
114
116
|
c.tsw.WriteLinef("%s: $.VarRef<%s>;", fieldKeyName, fieldTsType)
|
|
115
117
|
}
|
|
116
118
|
c.tsw.Indent(-1)
|
|
@@ -346,7 +348,16 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
346
348
|
}
|
|
347
349
|
|
|
348
350
|
// Promoted methods
|
|
349
|
-
|
|
351
|
+
// Use pointer to embedded type to get both value and pointer receiver methods
|
|
352
|
+
// This matches Go's behavior where embedding T promotes both T and *T methods
|
|
353
|
+
// Exception: For interfaces, use the interface directly as pointer-to-interface has no methods
|
|
354
|
+
methodSetType := embeddedFieldType
|
|
355
|
+
if _, isPtr := embeddedFieldType.(*types.Pointer); !isPtr {
|
|
356
|
+
if _, isInterface := embeddedFieldType.Underlying().(*types.Interface); !isInterface {
|
|
357
|
+
methodSetType = types.NewPointer(embeddedFieldType)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
embeddedMethodSet := types.NewMethodSet(methodSetType)
|
|
350
361
|
for k := range embeddedMethodSet.Len() {
|
|
351
362
|
methodSelection := embeddedMethodSet.At(k)
|
|
352
363
|
method := methodSelection.Obj().(*types.Func)
|
|
@@ -479,13 +490,23 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
479
490
|
if fieldKeyName == "_" {
|
|
480
491
|
continue
|
|
481
492
|
}
|
|
482
|
-
// fieldTsType := c.getTypeString(field.Type())
|
|
483
493
|
if !firstField {
|
|
484
494
|
c.tsw.WriteLiterally(", ")
|
|
485
495
|
}
|
|
486
496
|
firstField = false
|
|
487
497
|
c.tsw.WriteLiterallyf("%q: ", fieldKeyName)
|
|
488
|
-
|
|
498
|
+
|
|
499
|
+
// Get the struct tag for this field
|
|
500
|
+
tag := underlyingStruct.Tag(i)
|
|
501
|
+
if tag != "" {
|
|
502
|
+
// Write field info with tag as StructFieldInfo object
|
|
503
|
+
c.tsw.WriteLiterally("{ type: ")
|
|
504
|
+
c.writeTypeInfoObject(field.Type())
|
|
505
|
+
c.tsw.WriteLiterallyf(", tag: %q }", tag)
|
|
506
|
+
} else {
|
|
507
|
+
// No tag, write type info directly for backwards compatibility
|
|
508
|
+
c.writeTypeInfoObject(field.Type())
|
|
509
|
+
}
|
|
489
510
|
}
|
|
490
511
|
c.tsw.WriteLiterally("}")
|
|
491
512
|
c.tsw.WriteLine("")
|
|
@@ -543,15 +564,16 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
|
|
|
543
564
|
fieldType = ptr.Elem()
|
|
544
565
|
}
|
|
545
566
|
|
|
546
|
-
if
|
|
547
|
-
embeddedName := named.Obj().Name()
|
|
567
|
+
if _, ok := fieldType.(*types.Named); ok {
|
|
548
568
|
// Check if the embedded type is an interface
|
|
549
569
|
if _, isInterface := fieldType.Underlying().(*types.Interface); isInterface {
|
|
550
570
|
// For embedded interfaces, use the full qualified interface type
|
|
551
571
|
embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = c.getTypeString(field.Type())
|
|
552
572
|
} else {
|
|
553
573
|
// For embedded structs, use ConstructorParameters for field-based initialization
|
|
554
|
-
|
|
574
|
+
// Use getTypeString to get the qualified type name (e.g., bytes.Buffer not just Buffer)
|
|
575
|
+
qualifiedTypeName := c.getTypeString(fieldType)
|
|
576
|
+
embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = fmt.Sprintf("Partial<ConstructorParameters<typeof %s>[0]>", qualifiedTypeName)
|
|
555
577
|
}
|
|
556
578
|
}
|
|
557
579
|
continue
|
|
@@ -573,7 +595,7 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
|
|
|
573
595
|
for name := range fieldMap {
|
|
574
596
|
fieldNames = append(fieldNames, name)
|
|
575
597
|
}
|
|
576
|
-
|
|
598
|
+
slices.Sort(fieldNames)
|
|
577
599
|
|
|
578
600
|
var fieldDefs []string
|
|
579
601
|
for _, fieldName := range fieldNames {
|
package/compiler/spec-value.go
CHANGED
|
@@ -164,7 +164,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
|
|
|
164
164
|
isInsideFunction = nodeInfo.IsInsideFunction
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
if
|
|
167
|
+
if !isInsideFunction {
|
|
168
168
|
c.tsw.WriteLiterally("export ")
|
|
169
169
|
}
|
|
170
170
|
c.tsw.WriteLiterally("let ")
|
|
@@ -493,7 +493,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
|
|
|
493
493
|
isInsideFunction = nodeInfo.IsInsideFunction
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
-
if
|
|
496
|
+
if !isInsideFunction {
|
|
497
497
|
c.tsw.WriteLiterally("export ")
|
|
498
498
|
}
|
|
499
499
|
|
package/compiler/spec.go
CHANGED
|
@@ -57,6 +57,7 @@ func (c *GoToTSCompiler) getEmbeddedFieldKeyName(fieldType types.Type) string {
|
|
|
57
57
|
|
|
58
58
|
func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Type, doc, comment *ast.CommentGroup, astType ast.Expr) {
|
|
59
59
|
// Use AST type information if available to preserve qualified names
|
|
60
|
+
// Note: getASTTypeString/getTypeString already includes "null |" for interface types
|
|
60
61
|
var fieldTypeStr string
|
|
61
62
|
if astType != nil {
|
|
62
63
|
fieldTypeStr = c.getASTTypeString(astType, fieldType)
|
|
@@ -406,11 +407,20 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
|
|
|
406
407
|
c.WriteTypeExpr(funcDecl.Type.Results.List[0].Type)
|
|
407
408
|
} else {
|
|
408
409
|
c.tsw.WriteLiterally("[")
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
first := true
|
|
411
|
+
for _, field := range funcDecl.Type.Results.List {
|
|
412
|
+
// Each field may represent multiple return values (e.g., "a, b int")
|
|
413
|
+
count := len(field.Names)
|
|
414
|
+
if count == 0 {
|
|
415
|
+
count = 1 // Unnamed return value
|
|
416
|
+
}
|
|
417
|
+
for j := 0; j < count; j++ {
|
|
418
|
+
if !first {
|
|
419
|
+
c.tsw.WriteLiterally(", ")
|
|
420
|
+
}
|
|
421
|
+
first = false
|
|
422
|
+
c.WriteTypeExpr(field.Type)
|
|
412
423
|
}
|
|
413
|
-
c.WriteTypeExpr(field.Type)
|
|
414
424
|
}
|
|
415
425
|
c.tsw.WriteLiterally("]")
|
|
416
426
|
}
|
|
@@ -625,6 +635,15 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
|
625
635
|
importVars: make(map[string]struct{}),
|
|
626
636
|
}
|
|
627
637
|
|
|
638
|
+
// Skip writing the import if it was already written as a synthetic import
|
|
639
|
+
// This prevents duplicate imports when a file needs an import both from
|
|
640
|
+
// its AST and for promoted methods from embedded structs
|
|
641
|
+
if syntheticImports := c.analysis.SyntheticImportsPerFile[c.currentFilePath]; syntheticImports != nil {
|
|
642
|
+
if _, isSynthetic := syntheticImports[impName]; isSynthetic {
|
|
643
|
+
return
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
628
647
|
c.tsw.WriteImport(impName, tsImportPath+"/index.js")
|
|
629
648
|
}
|
|
630
649
|
|