goscript 0.0.50 → 0.0.51
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/compiler/analysis.go +18 -4
- package/compiler/compiler.go +8 -4
- package/compiler/expr-call-async.go +218 -13
- package/compiler/sanitize.go +1 -2
- package/compiler/spec-struct.go +3 -3
- package/compiler/spec.go +0 -21
- package/compiler/stmt-select.go +52 -1
- package/compiler/type.go +3 -3
- package/dist/gs/builtin/channel.d.ts +2 -2
- package/dist/gs/builtin/channel.js +12 -7
- package/dist/gs/builtin/channel.js.map +1 -1
- package/dist/gs/syscall/constants.d.ts +24 -0
- package/dist/gs/syscall/constants.js +27 -0
- package/dist/gs/syscall/constants.js.map +1 -0
- package/dist/gs/syscall/env.d.ts +6 -0
- package/dist/gs/syscall/env.js +43 -0
- package/dist/gs/syscall/env.js.map +1 -0
- package/dist/gs/syscall/errors.d.ts +111 -0
- package/dist/gs/syscall/errors.js +547 -0
- package/dist/gs/syscall/errors.js.map +1 -0
- package/dist/gs/syscall/fs.d.ts +29 -0
- package/dist/gs/syscall/fs.js +53 -0
- package/dist/gs/syscall/fs.js.map +1 -0
- package/dist/gs/syscall/index.d.ts +6 -80
- package/dist/gs/syscall/index.js +12 -168
- package/dist/gs/syscall/index.js.map +1 -1
- package/dist/gs/syscall/rawconn.d.ts +7 -0
- package/dist/gs/syscall/rawconn.js +19 -0
- package/dist/gs/syscall/rawconn.js.map +1 -0
- package/dist/gs/syscall/types.d.ts +12 -0
- package/dist/gs/syscall/types.js +2 -0
- package/dist/gs/syscall/types.js.map +1 -0
- package/gs/builtin/channel.ts +18 -12
- package/gs/syscall/constants.ts +29 -0
- package/gs/syscall/env.ts +47 -0
- package/gs/syscall/errors.ts +658 -0
- package/gs/syscall/fs.ts +62 -0
- package/gs/syscall/index.ts +12 -207
- package/gs/syscall/rawconn.ts +23 -0
- package/gs/syscall/types.ts +18 -0
- package/package.json +1 -1
package/compiler/analysis.go
CHANGED
|
@@ -911,6 +911,11 @@ func (v *analysisVisitor) containsAsyncOperations(node ast.Node) bool {
|
|
|
911
911
|
return false
|
|
912
912
|
}
|
|
913
913
|
|
|
914
|
+
case *ast.SelectStmt:
|
|
915
|
+
// Select statement with channel operations
|
|
916
|
+
hasAsync = true
|
|
917
|
+
return false
|
|
918
|
+
|
|
914
919
|
case *ast.CallExpr:
|
|
915
920
|
// Check if we're calling a function known to be async
|
|
916
921
|
if funcIdent, ok := s.Fun.(*ast.Ident); ok {
|
|
@@ -928,8 +933,18 @@ func (v *analysisVisitor) containsAsyncOperations(node ast.Node) bool {
|
|
|
928
933
|
// Get the type of the receiver
|
|
929
934
|
if obj := v.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
930
935
|
if varObj, ok := obj.(*types.Var); ok {
|
|
931
|
-
//
|
|
932
|
-
|
|
936
|
+
// Handle both direct named types and pointer to named types
|
|
937
|
+
var namedType *types.Named
|
|
938
|
+
switch t := varObj.Type().(type) {
|
|
939
|
+
case *types.Named:
|
|
940
|
+
namedType = t
|
|
941
|
+
case *types.Pointer:
|
|
942
|
+
if nt, isNamed := t.Elem().(*types.Named); isNamed {
|
|
943
|
+
namedType = nt
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if namedType != nil {
|
|
933
948
|
typeName := namedType.Obj().Name()
|
|
934
949
|
methodName := selExpr.Sel.Name
|
|
935
950
|
|
|
@@ -944,13 +959,12 @@ func (v *analysisVisitor) containsAsyncOperations(node ast.Node) bool {
|
|
|
944
959
|
return false
|
|
945
960
|
}
|
|
946
961
|
}
|
|
962
|
+
// Note: Local method async detection is handled during code generation to avoid circular dependencies
|
|
947
963
|
}
|
|
948
964
|
}
|
|
949
965
|
}
|
|
950
966
|
}
|
|
951
967
|
}
|
|
952
|
-
|
|
953
|
-
// TODO: Add detection of method calls on async types
|
|
954
968
|
}
|
|
955
969
|
|
|
956
970
|
return true
|
package/compiler/compiler.go
CHANGED
|
@@ -433,7 +433,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
|
433
433
|
switch d := decl.(type) {
|
|
434
434
|
case *ast.FuncDecl:
|
|
435
435
|
if d.Recv == nil && d.Name.IsExported() {
|
|
436
|
-
valueSymbols = append(valueSymbols, d.Name.Name)
|
|
436
|
+
valueSymbols = append(valueSymbols, sanitizeIdentifier(d.Name.Name))
|
|
437
437
|
}
|
|
438
438
|
case *ast.GenDecl:
|
|
439
439
|
for _, spec := range d.Specs {
|
|
@@ -443,17 +443,17 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
|
443
443
|
// Check if this is a struct type
|
|
444
444
|
if _, isStruct := s.Type.(*ast.StructType); isStruct {
|
|
445
445
|
// Structs become TypeScript classes and need both type and value exports
|
|
446
|
-
structSymbols = append(structSymbols, s.Name.Name)
|
|
446
|
+
structSymbols = append(structSymbols, sanitizeIdentifier(s.Name.Name))
|
|
447
447
|
} else {
|
|
448
448
|
// Other type declarations (interfaces, type definitions, type aliases)
|
|
449
449
|
// become TypeScript types and must be exported with "export type"
|
|
450
|
-
typeSymbols = append(typeSymbols, s.Name.Name)
|
|
450
|
+
typeSymbols = append(typeSymbols, sanitizeIdentifier(s.Name.Name))
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
case *ast.ValueSpec:
|
|
454
454
|
for _, name := range s.Names {
|
|
455
455
|
if name.IsExported() {
|
|
456
|
-
valueSymbols = append(valueSymbols, name.Name)
|
|
456
|
+
valueSymbols = append(valueSymbols, sanitizeIdentifier(name.Name))
|
|
457
457
|
}
|
|
458
458
|
}
|
|
459
459
|
}
|
|
@@ -676,6 +676,10 @@ type GoToTSCompiler struct {
|
|
|
676
676
|
pkg *packages.Package
|
|
677
677
|
|
|
678
678
|
analysis *Analysis
|
|
679
|
+
|
|
680
|
+
// awaitedCalls tracks which call expressions have already been processed
|
|
681
|
+
// to avoid adding double await keywords
|
|
682
|
+
awaitedCalls map[*ast.CallExpr]bool
|
|
679
683
|
}
|
|
680
684
|
|
|
681
685
|
// It initializes the compiler with a `TSCodeWriter` for output,
|
|
@@ -2,6 +2,7 @@ package compiler
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"go/ast"
|
|
5
|
+
"go/token"
|
|
5
6
|
"go/types"
|
|
6
7
|
"strings"
|
|
7
8
|
)
|
|
@@ -9,10 +10,19 @@ import (
|
|
|
9
10
|
// writeAsyncCallIfNeeded writes the await prefix for async function or method calls
|
|
10
11
|
// Returns true if await was written, false otherwise
|
|
11
12
|
func (c *GoToTSCompiler) writeAsyncCallIfNeeded(exp *ast.CallExpr) bool {
|
|
13
|
+
// Track if we've already processed this call expression to avoid double await
|
|
14
|
+
if c.awaitedCalls == nil {
|
|
15
|
+
c.awaitedCalls = make(map[*ast.CallExpr]bool)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if c.awaitedCalls[exp] {
|
|
19
|
+
return false // Already processed this call
|
|
20
|
+
}
|
|
12
21
|
switch fun := exp.Fun.(type) {
|
|
13
22
|
case *ast.Ident:
|
|
14
23
|
// Function call (e.g., func())
|
|
15
24
|
if obj := c.pkg.TypesInfo.Uses[fun]; obj != nil && c.analysis.IsAsyncFunc(obj) {
|
|
25
|
+
c.awaitedCalls[exp] = true
|
|
16
26
|
c.tsw.WriteLiterally("await ")
|
|
17
27
|
return true
|
|
18
28
|
}
|
|
@@ -20,8 +30,23 @@ func (c *GoToTSCompiler) writeAsyncCallIfNeeded(exp *ast.CallExpr) bool {
|
|
|
20
30
|
|
|
21
31
|
case *ast.SelectorExpr:
|
|
22
32
|
// Method call (e.g., obj.method())
|
|
23
|
-
ident
|
|
24
|
-
|
|
33
|
+
var ident *ast.Ident
|
|
34
|
+
var identOk bool
|
|
35
|
+
|
|
36
|
+
// Handle both direct identifiers and pointer dereferences
|
|
37
|
+
switch x := fun.X.(type) {
|
|
38
|
+
case *ast.Ident:
|
|
39
|
+
ident, identOk = x, true
|
|
40
|
+
case *ast.StarExpr:
|
|
41
|
+
// Handle pointer dereference: (*p).method() or p.method() where p is a pointer
|
|
42
|
+
if id, isIdent := x.X.(*ast.Ident); isIdent {
|
|
43
|
+
ident, identOk = id, true
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if !identOk {
|
|
25
50
|
return false
|
|
26
51
|
}
|
|
27
52
|
|
|
@@ -37,8 +62,20 @@ func (c *GoToTSCompiler) writeAsyncCallIfNeeded(exp *ast.CallExpr) bool {
|
|
|
37
62
|
}
|
|
38
63
|
|
|
39
64
|
// Get the type name and package
|
|
40
|
-
namedType
|
|
41
|
-
|
|
65
|
+
var namedType *types.Named
|
|
66
|
+
var namedTypeOk bool
|
|
67
|
+
|
|
68
|
+
// Handle both direct named types and pointer to named types
|
|
69
|
+
switch t := varObj.Type().(type) {
|
|
70
|
+
case *types.Named:
|
|
71
|
+
namedType, namedTypeOk = t, true
|
|
72
|
+
case *types.Pointer:
|
|
73
|
+
if nt, isNamed := t.Elem().(*types.Named); isNamed {
|
|
74
|
+
namedType, namedTypeOk = nt, true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if !namedTypeOk {
|
|
42
79
|
return false
|
|
43
80
|
}
|
|
44
81
|
|
|
@@ -47,23 +84,191 @@ func (c *GoToTSCompiler) writeAsyncCallIfNeeded(exp *ast.CallExpr) bool {
|
|
|
47
84
|
|
|
48
85
|
// Check if the type is from an imported package
|
|
49
86
|
typePkg := namedType.Obj().Pkg()
|
|
50
|
-
if typePkg
|
|
51
|
-
|
|
87
|
+
if typePkg != nil && typePkg != c.pkg.Types {
|
|
88
|
+
// Use the full package path from the type information (not just the package name)
|
|
89
|
+
pkgPath := typePkg.Path()
|
|
90
|
+
|
|
91
|
+
// Check if this method is async based on metadata
|
|
92
|
+
if c.analysis.IsMethodAsync(pkgPath, typeName, methodName) {
|
|
93
|
+
c.awaitedCalls[exp] = true
|
|
94
|
+
c.tsw.WriteLiterally("await ")
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// For local types, check if the method contains async operations at runtime
|
|
99
|
+
// to avoid circular dependencies during analysis
|
|
100
|
+
if c.isLocalMethodAsync(namedType, methodName) {
|
|
101
|
+
c.awaitedCalls[exp] = true
|
|
102
|
+
c.tsw.WriteLiterally("await ")
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
52
105
|
}
|
|
106
|
+
return false
|
|
53
107
|
|
|
54
|
-
|
|
55
|
-
|
|
108
|
+
default:
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
56
112
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
113
|
+
// isLocalMethodAsync checks if a local method contains async operations by inspecting its body
|
|
114
|
+
func (c *GoToTSCompiler) isLocalMethodAsync(namedType *types.Named, methodName string) bool {
|
|
115
|
+
// Find the method in the named type
|
|
116
|
+
for i := 0; i < namedType.NumMethods(); i++ {
|
|
117
|
+
method := namedType.Method(i)
|
|
118
|
+
if method.Name() == methodName {
|
|
119
|
+
// Find the method declaration in the AST
|
|
120
|
+
methodDecl := c.findMethodDecl(namedType, methodName)
|
|
121
|
+
if methodDecl != nil && methodDecl.Body != nil {
|
|
122
|
+
// Check if the method body contains async operations
|
|
123
|
+
return c.containsAsyncOperationsRuntime(methodDecl.Body)
|
|
124
|
+
}
|
|
125
|
+
break
|
|
61
126
|
}
|
|
127
|
+
}
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// findMethodDecl finds the AST declaration for a method on a named type
|
|
132
|
+
func (c *GoToTSCompiler) findMethodDecl(namedType *types.Named, methodName string) *ast.FuncDecl {
|
|
133
|
+
// Search through all files in the package for the method declaration
|
|
134
|
+
for _, file := range c.pkg.Syntax {
|
|
135
|
+
for _, decl := range file.Decls {
|
|
136
|
+
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
137
|
+
// Check if this is a method with the right name
|
|
138
|
+
if funcDecl.Name.Name == methodName && funcDecl.Recv != nil {
|
|
139
|
+
// Check if the receiver type matches
|
|
140
|
+
if c.isReceiverOfType(funcDecl, namedType) {
|
|
141
|
+
return funcDecl
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return nil
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// isReceiverOfType checks if a function declaration is a method of the given named type
|
|
151
|
+
func (c *GoToTSCompiler) isReceiverOfType(funcDecl *ast.FuncDecl, namedType *types.Named) bool {
|
|
152
|
+
if funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 {
|
|
62
153
|
return false
|
|
154
|
+
}
|
|
63
155
|
|
|
64
|
-
|
|
156
|
+
recvField := funcDecl.Recv.List[0]
|
|
157
|
+
if len(recvField.Names) == 0 {
|
|
65
158
|
return false
|
|
66
159
|
}
|
|
160
|
+
|
|
161
|
+
recvIdent := recvField.Names[0]
|
|
162
|
+
if obj := c.pkg.TypesInfo.Defs[recvIdent]; obj != nil {
|
|
163
|
+
if varObj, ok := obj.(*types.Var); ok {
|
|
164
|
+
// Handle both direct named types and pointer to named types
|
|
165
|
+
var receiverNamedType *types.Named
|
|
166
|
+
switch t := varObj.Type().(type) {
|
|
167
|
+
case *types.Named:
|
|
168
|
+
receiverNamedType = t
|
|
169
|
+
case *types.Pointer:
|
|
170
|
+
if nt, isNamed := t.Elem().(*types.Named); isNamed {
|
|
171
|
+
receiverNamedType = nt
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if receiverNamedType != nil {
|
|
176
|
+
// For generic types, compare the origin types and names
|
|
177
|
+
// since instantiated generics have different type objects
|
|
178
|
+
targetObj := namedType.Obj()
|
|
179
|
+
receiverObj := receiverNamedType.Obj()
|
|
180
|
+
|
|
181
|
+
// Compare by name and package path
|
|
182
|
+
return targetObj.Name() == receiverObj.Name() &&
|
|
183
|
+
targetObj.Pkg() == receiverObj.Pkg()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// containsAsyncOperationsRuntime checks if a node contains async operations at runtime
|
|
192
|
+
func (c *GoToTSCompiler) containsAsyncOperationsRuntime(node ast.Node) bool {
|
|
193
|
+
var hasAsync bool
|
|
194
|
+
|
|
195
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
196
|
+
if n == nil {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
switch s := n.(type) {
|
|
201
|
+
case *ast.SendStmt:
|
|
202
|
+
// Channel send operation (ch <- value)
|
|
203
|
+
hasAsync = true
|
|
204
|
+
return false
|
|
205
|
+
|
|
206
|
+
case *ast.UnaryExpr:
|
|
207
|
+
// Channel receive operation (<-ch)
|
|
208
|
+
if s.Op == token.ARROW {
|
|
209
|
+
hasAsync = true
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
case *ast.SelectStmt:
|
|
214
|
+
// Select statement with channel operations
|
|
215
|
+
hasAsync = true
|
|
216
|
+
return false
|
|
217
|
+
|
|
218
|
+
case *ast.CallExpr:
|
|
219
|
+
// Check if we're calling a function known to be async
|
|
220
|
+
if funcIdent, ok := s.Fun.(*ast.Ident); ok {
|
|
221
|
+
// Get the object for this function call
|
|
222
|
+
if obj := c.pkg.TypesInfo.Uses[funcIdent]; obj != nil && c.analysis.IsAsyncFunc(obj) {
|
|
223
|
+
hasAsync = true
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for method calls on imported types (similar to analysis.go logic)
|
|
229
|
+
if selExpr, ok := s.Fun.(*ast.SelectorExpr); ok {
|
|
230
|
+
if ident, ok := selExpr.X.(*ast.Ident); ok {
|
|
231
|
+
if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
232
|
+
if varObj, ok := obj.(*types.Var); ok {
|
|
233
|
+
var namedType *types.Named
|
|
234
|
+
switch t := varObj.Type().(type) {
|
|
235
|
+
case *types.Named:
|
|
236
|
+
namedType = t
|
|
237
|
+
case *types.Pointer:
|
|
238
|
+
if nt, isNamed := t.Elem().(*types.Named); isNamed {
|
|
239
|
+
namedType = nt
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if namedType != nil {
|
|
244
|
+
typeName := namedType.Obj().Name()
|
|
245
|
+
methodName := selExpr.Sel.Name
|
|
246
|
+
|
|
247
|
+
// Check if the type is from an imported package
|
|
248
|
+
if typePkg := namedType.Obj().Pkg(); typePkg != nil && typePkg != c.pkg.Types {
|
|
249
|
+
pkgPath := typePkg.Path()
|
|
250
|
+
if c.analysis.IsMethodAsync(pkgPath, typeName, methodName) {
|
|
251
|
+
hasAsync = true
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
// For local types, recursively check
|
|
256
|
+
if c.isLocalMethodAsync(namedType, methodName) {
|
|
257
|
+
hasAsync = true
|
|
258
|
+
return false
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return true
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
return hasAsync
|
|
67
272
|
}
|
|
68
273
|
|
|
69
274
|
// addNonNullAssertion adds ! for function calls that might return null
|
package/compiler/sanitize.go
CHANGED
|
@@ -11,8 +11,7 @@ func sanitizeIdentifier(name string) string {
|
|
|
11
11
|
|
|
12
12
|
// Handle TypeScript built-in types that conflict with Go type parameter names
|
|
13
13
|
builtinTypes := map[string]string{
|
|
14
|
-
"
|
|
15
|
-
// Add other built-in types as needed
|
|
14
|
+
"Promise": "PromiseType",
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
if replacement, exists := builtinTypes[name]; exists {
|
package/compiler/spec-struct.go
CHANGED
|
@@ -37,7 +37,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
37
37
|
c.tsw.WriteLine("{")
|
|
38
38
|
c.tsw.Indent(1)
|
|
39
39
|
|
|
40
|
-
className := a.Name.Name
|
|
40
|
+
className := sanitizeIdentifier(a.Name.Name)
|
|
41
41
|
|
|
42
42
|
goStructType, ok := c.pkg.TypesInfo.Defs[a.Name].Type().(*types.Named)
|
|
43
43
|
if !ok {
|
|
@@ -231,10 +231,10 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
231
231
|
// Check for both simple identifiers (Pair) and generic types (Pair[T])
|
|
232
232
|
var recvTypeName string
|
|
233
233
|
if ident, ok := recvType.(*ast.Ident); ok {
|
|
234
|
-
recvTypeName = ident.Name
|
|
234
|
+
recvTypeName = sanitizeIdentifier(ident.Name)
|
|
235
235
|
} else if indexExpr, ok := recvType.(*ast.IndexExpr); ok {
|
|
236
236
|
if ident, ok := indexExpr.X.(*ast.Ident); ok {
|
|
237
|
-
recvTypeName = ident.Name
|
|
237
|
+
recvTypeName = sanitizeIdentifier(ident.Name)
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
package/compiler/spec.go
CHANGED
|
@@ -136,11 +136,6 @@ func (c *GoToTSCompiler) writeRegularFieldInitializer(fieldName string, fieldTyp
|
|
|
136
136
|
|
|
137
137
|
c.tsw.WriteLiterallyf("init?.%s ?? ", fieldName)
|
|
138
138
|
|
|
139
|
-
// Debug for Mode field specifically
|
|
140
|
-
if fieldName == "Mode" {
|
|
141
|
-
fmt.Printf("DEBUG Mode field: Type=%T, String=%s\n", fieldType, fieldType.String())
|
|
142
|
-
}
|
|
143
|
-
|
|
144
139
|
// Priority 1: Check if this is a wrapper type
|
|
145
140
|
if c.analysis.IsNamedBasicType(fieldType) {
|
|
146
141
|
// For wrapper types, use the zero value of the underlying type with type casting
|
|
@@ -161,35 +156,22 @@ func (c *GoToTSCompiler) writeRegularFieldInitializer(fieldName string, fieldTyp
|
|
|
161
156
|
|
|
162
157
|
// Priority 2: Handle imported types with basic underlying types (like os.FileMode)
|
|
163
158
|
if c.isImportedBasicType(fieldType) {
|
|
164
|
-
if fieldName == "Mode" {
|
|
165
|
-
fmt.Printf("DEBUG Mode field: Using imported basic type zero value\n")
|
|
166
|
-
}
|
|
167
159
|
c.writeImportedBasicTypeZeroValue(fieldType)
|
|
168
160
|
return
|
|
169
161
|
}
|
|
170
162
|
|
|
171
163
|
// Priority 3: Handle named types
|
|
172
164
|
if named, isNamed := fieldType.(*types.Named); isNamed {
|
|
173
|
-
if fieldName == "Mode" {
|
|
174
|
-
fmt.Printf("DEBUG Mode field: Using named type zero value\n")
|
|
175
|
-
}
|
|
176
165
|
c.writeNamedTypeZeroValue(named)
|
|
177
166
|
return
|
|
178
167
|
}
|
|
179
168
|
|
|
180
169
|
// Priority 4: Handle type aliases
|
|
181
170
|
if alias, isAlias := fieldType.(*types.Alias); isAlias {
|
|
182
|
-
if fieldName == "Mode" {
|
|
183
|
-
fmt.Printf("DEBUG Mode field: Using type alias zero value\n")
|
|
184
|
-
}
|
|
185
171
|
c.writeTypeAliasZeroValue(alias, astType)
|
|
186
172
|
return
|
|
187
173
|
}
|
|
188
174
|
|
|
189
|
-
// Default: use WriteZeroValueForType
|
|
190
|
-
if fieldName == "Mode" {
|
|
191
|
-
fmt.Printf("DEBUG Mode field: Using default WriteZeroValueForType\n")
|
|
192
|
-
}
|
|
193
175
|
c.WriteZeroValueForType(fieldType)
|
|
194
176
|
}
|
|
195
177
|
|
|
@@ -241,7 +223,6 @@ func (c *GoToTSCompiler) isImportedBasicType(fieldType types.Type) bool {
|
|
|
241
223
|
func (c *GoToTSCompiler) writeImportedBasicTypeZeroValue(fieldType types.Type) {
|
|
242
224
|
if named, ok := fieldType.(*types.Named); ok {
|
|
243
225
|
underlying := named.Underlying()
|
|
244
|
-
fmt.Printf("DEBUG writeImportedBasicTypeZeroValue: Named type, underlying=%T\n", underlying)
|
|
245
226
|
// Write zero value of underlying type with type casting
|
|
246
227
|
c.WriteZeroValueForType(underlying)
|
|
247
228
|
c.tsw.WriteLiterally(" as ")
|
|
@@ -251,7 +232,6 @@ func (c *GoToTSCompiler) writeImportedBasicTypeZeroValue(fieldType types.Type) {
|
|
|
251
232
|
|
|
252
233
|
if alias, ok := fieldType.(*types.Alias); ok {
|
|
253
234
|
underlying := alias.Underlying()
|
|
254
|
-
fmt.Printf("DEBUG writeImportedBasicTypeZeroValue: Alias type, underlying=%T\n", underlying)
|
|
255
235
|
// Write zero value of underlying type with type casting
|
|
256
236
|
c.WriteZeroValueForType(underlying)
|
|
257
237
|
c.tsw.WriteLiterally(" as ")
|
|
@@ -260,7 +240,6 @@ func (c *GoToTSCompiler) writeImportedBasicTypeZeroValue(fieldType types.Type) {
|
|
|
260
240
|
}
|
|
261
241
|
|
|
262
242
|
// Fallback (should not happen if isImportedBasicType was correct)
|
|
263
|
-
fmt.Printf("DEBUG writeImportedBasicTypeZeroValue: Fallback path\n")
|
|
264
243
|
c.WriteZeroValueForType(fieldType)
|
|
265
244
|
}
|
|
266
245
|
|
package/compiler/stmt-select.go
CHANGED
|
@@ -8,6 +8,18 @@ import (
|
|
|
8
8
|
"github.com/pkg/errors"
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
// caseEndsWithReturn checks if a case body ends with a return statement
|
|
12
|
+
func (c *GoToTSCompiler) caseEndsWithReturn(body []ast.Stmt) bool {
|
|
13
|
+
if len(body) == 0 {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Check if the last statement is a return statement
|
|
18
|
+
lastStmt := body[len(body)-1]
|
|
19
|
+
_, isReturn := lastStmt.(*ast.ReturnStmt)
|
|
20
|
+
return isReturn
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
// WriteStmtSelect translates a Go `select` statement into an asynchronous
|
|
12
24
|
// TypeScript operation using the `$.selectStatement` runtime helper.
|
|
13
25
|
// Go's `select` provides non-deterministic choice over channel operations.
|
|
@@ -38,8 +50,29 @@ func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
|
|
|
38
50
|
// Variable to track whether we have a default case
|
|
39
51
|
hasDefault := false
|
|
40
52
|
|
|
53
|
+
// Analyze if all cases end with return statements
|
|
54
|
+
allCasesReturn := true
|
|
55
|
+
for _, stmt := range exp.Body.List {
|
|
56
|
+
if commClause, ok := stmt.(*ast.CommClause); ok {
|
|
57
|
+
if commClause.Comm == nil {
|
|
58
|
+
// Default case - check if it ends with return
|
|
59
|
+
if !c.caseEndsWithReturn(commClause.Body) {
|
|
60
|
+
allCasesReturn = false
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
// Regular case - check if it ends with return
|
|
64
|
+
if !c.caseEndsWithReturn(commClause.Body) {
|
|
65
|
+
allCasesReturn = false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Generate unique variable names for this select statement
|
|
72
|
+
selectID := fmt.Sprintf("%d", exp.Pos()) // Use AST position for uniqueness
|
|
73
|
+
|
|
41
74
|
// Start the selectStatement call and the array literal
|
|
42
|
-
c.tsw.
|
|
75
|
+
c.tsw.WriteLiterallyf("const [_selectHasReturn%s, _selectValue%s] = await $.selectStatement(", selectID, selectID)
|
|
43
76
|
c.tsw.WriteLine("[") // Put bracket on new line
|
|
44
77
|
c.tsw.Indent(1)
|
|
45
78
|
|
|
@@ -207,5 +240,23 @@ func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
|
|
|
207
240
|
c.tsw.WriteLiterally(")")
|
|
208
241
|
c.tsw.WriteLine("")
|
|
209
242
|
|
|
243
|
+
// Add code to handle the return value from selectStatement
|
|
244
|
+
c.tsw.WriteLiterallyf("if (_selectHasReturn%s) {", selectID)
|
|
245
|
+
c.tsw.WriteLine("")
|
|
246
|
+
c.tsw.Indent(1)
|
|
247
|
+
c.tsw.WriteLiterallyf("return _selectValue%s!", selectID)
|
|
248
|
+
c.tsw.WriteLine("")
|
|
249
|
+
c.tsw.Indent(-1)
|
|
250
|
+
c.tsw.WriteLine("}")
|
|
251
|
+
|
|
252
|
+
// If all cases return, add a TypeScript-satisfying fallback return
|
|
253
|
+
if allCasesReturn {
|
|
254
|
+
c.tsw.WriteLine("// All cases should return, this fallback should never execute")
|
|
255
|
+
c.tsw.WriteLine("throw new Error('Unexpected: select statement did not return when all cases should return')")
|
|
256
|
+
} else {
|
|
257
|
+
c.tsw.WriteLiterallyf("// If _selectHasReturn%s is false, continue execution", selectID)
|
|
258
|
+
c.tsw.WriteLine("")
|
|
259
|
+
}
|
|
260
|
+
|
|
210
261
|
return nil
|
|
211
262
|
}
|
package/compiler/type.go
CHANGED
|
@@ -278,7 +278,7 @@ func (c *GoToTSCompiler) WriteNamedType(t *types.Named) {
|
|
|
278
278
|
// Write the qualified name: importAlias.TypeName
|
|
279
279
|
c.tsw.WriteLiterally(importAlias)
|
|
280
280
|
c.tsw.WriteLiterally(".")
|
|
281
|
-
c.tsw.WriteLiterally(t.Obj().Name())
|
|
281
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(t.Obj().Name()))
|
|
282
282
|
|
|
283
283
|
// For generic types, include type arguments
|
|
284
284
|
if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 {
|
|
@@ -306,7 +306,7 @@ func (c *GoToTSCompiler) WriteNamedType(t *types.Named) {
|
|
|
306
306
|
// Write the qualified name: importAlias.TypeName
|
|
307
307
|
c.tsw.WriteLiterally(importAlias)
|
|
308
308
|
c.tsw.WriteLiterally(".")
|
|
309
|
-
c.tsw.WriteLiterally(t.Obj().Name())
|
|
309
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(t.Obj().Name()))
|
|
310
310
|
|
|
311
311
|
// For generic types, include type arguments
|
|
312
312
|
if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 {
|
|
@@ -330,7 +330,7 @@ func (c *GoToTSCompiler) WriteNamedType(t *types.Named) {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// Use Obj().Name() for the original defined name (local types or unmatched imports)
|
|
333
|
-
c.tsw.WriteLiterally(t.Obj().Name())
|
|
333
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(t.Obj().Name()))
|
|
334
334
|
|
|
335
335
|
// For generic types, include type arguments
|
|
336
336
|
if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 {
|
|
@@ -77,7 +77,7 @@ export interface SelectCase<T> {
|
|
|
77
77
|
isSend: boolean;
|
|
78
78
|
channel: Channel<any> | ChannelRef<any> | null;
|
|
79
79
|
value?: any;
|
|
80
|
-
onSelected?: (result: SelectResult<T>) => Promise<
|
|
80
|
+
onSelected?: (result: SelectResult<T>) => Promise<any>;
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* Helper for 'select' statements. Takes an array of select cases
|
|
@@ -87,7 +87,7 @@ export interface SelectCase<T> {
|
|
|
87
87
|
* @param hasDefault Whether there is a default case
|
|
88
88
|
* @returns A promise that resolves with the result of the selected case
|
|
89
89
|
*/
|
|
90
|
-
export declare function selectStatement<T>(cases: SelectCase<T>[], hasDefault?: boolean): Promise<
|
|
90
|
+
export declare function selectStatement<T, V = void>(cases: SelectCase<T>[], hasDefault?: boolean): Promise<[boolean, V]>;
|
|
91
91
|
/**
|
|
92
92
|
* Helper function for channel send operations that handles nil channels correctly.
|
|
93
93
|
* In Go, sending to a nil channel blocks forever.
|
|
@@ -41,13 +41,15 @@ export async function selectStatement(cases, hasDefault = false) {
|
|
|
41
41
|
if (selectedCase.isSend) {
|
|
42
42
|
const result = await selectedCase.channel.selectSend(selectedCase.value, selectedCase.id);
|
|
43
43
|
if (selectedCase.onSelected) {
|
|
44
|
-
await selectedCase.onSelected(result);
|
|
44
|
+
const handlerResult = await selectedCase.onSelected(result);
|
|
45
|
+
return [handlerResult !== undefined, handlerResult];
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
else {
|
|
48
49
|
const result = await selectedCase.channel.selectReceive(selectedCase.id);
|
|
49
50
|
if (selectedCase.onSelected) {
|
|
50
|
-
await selectedCase.onSelected(result);
|
|
51
|
+
const handlerResult = await selectedCase.onSelected(result);
|
|
52
|
+
return [handlerResult !== undefined, handlerResult];
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
}
|
|
@@ -55,7 +57,7 @@ export async function selectStatement(cases, hasDefault = false) {
|
|
|
55
57
|
// This case should ideally not happen if channel is required for non-default cases
|
|
56
58
|
console.error('Selected case without a channel:', selectedCase);
|
|
57
59
|
}
|
|
58
|
-
return; // Return after executing a ready case
|
|
60
|
+
return [false, undefined]; // Return after executing a ready case
|
|
59
61
|
}
|
|
60
62
|
// 2. If no operations are ready and there's a default case, select default
|
|
61
63
|
if (hasDefault) {
|
|
@@ -63,13 +65,14 @@ export async function selectStatement(cases, hasDefault = false) {
|
|
|
63
65
|
const defaultCase = cases.find((c) => c.id === -1);
|
|
64
66
|
if (defaultCase && defaultCase.onSelected) {
|
|
65
67
|
// Execute the onSelected handler for the default case
|
|
66
|
-
await defaultCase.onSelected({
|
|
68
|
+
const handlerResult = await defaultCase.onSelected({
|
|
67
69
|
value: undefined,
|
|
68
70
|
ok: false,
|
|
69
71
|
id: -1,
|
|
70
|
-
});
|
|
72
|
+
});
|
|
73
|
+
return [handlerResult !== undefined, handlerResult];
|
|
71
74
|
}
|
|
72
|
-
return; // Return after executing the default case
|
|
75
|
+
return [false, undefined]; // Return after executing the default case
|
|
73
76
|
}
|
|
74
77
|
// 3. If no operations are ready and no default case, block until one is ready
|
|
75
78
|
// Use Promise.race on the blocking promises
|
|
@@ -94,9 +97,11 @@ export async function selectStatement(cases, hasDefault = false) {
|
|
|
94
97
|
// Execute onSelected handler for the selected case
|
|
95
98
|
const selectedCase = cases.find((c) => c.id === result.id);
|
|
96
99
|
if (selectedCase && selectedCase.onSelected) {
|
|
97
|
-
await selectedCase.onSelected(result);
|
|
100
|
+
const handlerResult = await selectedCase.onSelected(result);
|
|
101
|
+
return [handlerResult !== undefined, handlerResult];
|
|
98
102
|
}
|
|
99
103
|
// No explicit return needed here, as the function will implicitly return after the await
|
|
104
|
+
return [false, undefined];
|
|
100
105
|
}
|
|
101
106
|
/**
|
|
102
107
|
* Helper function for channel send operations that handles nil channels correctly.
|