goscript 0.0.83 → 0.1.0

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.
Files changed (197) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +27 -7
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +86 -0
  39. package/dist/gs/builtin/hostio.js +266 -0
  40. package/dist/gs/builtin/hostio.js.map +1 -0
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/print.d.ts +8 -0
  45. package/dist/gs/builtin/print.js +111 -0
  46. package/dist/gs/builtin/print.js.map +1 -0
  47. package/dist/gs/builtin/slice.d.ts +1 -1
  48. package/dist/gs/builtin/slice.js.map +1 -1
  49. package/dist/gs/builtin/type.d.ts +11 -0
  50. package/dist/gs/builtin/type.js +55 -1
  51. package/dist/gs/builtin/type.js.map +1 -1
  52. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  53. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  54. package/dist/gs/bytes/reader.gs.js.map +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/crypto/rand/index.d.ts +5 -0
  57. package/dist/gs/crypto/rand/index.js +77 -0
  58. package/dist/gs/crypto/rand/index.js.map +1 -0
  59. package/dist/gs/encoding/json/index.d.ts +3 -0
  60. package/dist/gs/encoding/json/index.js +160 -0
  61. package/dist/gs/encoding/json/index.js.map +1 -0
  62. package/dist/gs/fmt/fmt.js +2 -22
  63. package/dist/gs/fmt/fmt.js.map +1 -1
  64. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  65. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  66. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  67. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  68. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  69. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  70. package/dist/gs/go/scanner/index.d.ts +29 -0
  71. package/dist/gs/go/scanner/index.js +120 -0
  72. package/dist/gs/go/scanner/index.js.map +1 -0
  73. package/dist/gs/go/token/index.d.ts +31 -0
  74. package/dist/gs/go/token/index.js +82 -0
  75. package/dist/gs/go/token/index.js.map +1 -0
  76. package/dist/gs/internal/abi/index.js.map +1 -1
  77. package/dist/gs/io/fs/fs.js.map +1 -1
  78. package/dist/gs/io/fs/readdir.js.map +1 -1
  79. package/dist/gs/io/fs/readfile.js.map +1 -1
  80. package/dist/gs/io/fs/stat.js.map +1 -1
  81. package/dist/gs/io/fs/sub.js.map +1 -1
  82. package/dist/gs/io/io.js.map +1 -1
  83. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  84. package/dist/gs/os/error.gs.js +2 -4
  85. package/dist/gs/os/error.gs.js.map +1 -1
  86. package/dist/gs/os/exec.gs.js.map +1 -1
  87. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  88. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  89. package/dist/gs/os/root_js.gs.js.map +1 -1
  90. package/dist/gs/os/tempfile.gs.js +66 -9
  91. package/dist/gs/os/tempfile.gs.js.map +1 -1
  92. package/dist/gs/os/types.gs.js.map +1 -1
  93. package/dist/gs/os/types_js.gs.d.ts +2 -51
  94. package/dist/gs/os/types_js.gs.js +67 -105
  95. package/dist/gs/os/types_js.gs.js.map +1 -1
  96. package/dist/gs/os/types_unix.gs.js.map +1 -1
  97. package/dist/gs/path/filepath/match.js.map +1 -1
  98. package/dist/gs/path/match.js.map +1 -1
  99. package/dist/gs/path/path.js.map +1 -1
  100. package/dist/gs/reflect/index.d.ts +2 -2
  101. package/dist/gs/reflect/index.js +1 -1
  102. package/dist/gs/reflect/index.js.map +1 -1
  103. package/dist/gs/reflect/map.js.map +1 -1
  104. package/dist/gs/reflect/type.d.ts +2 -1
  105. package/dist/gs/reflect/type.js +85 -14
  106. package/dist/gs/reflect/type.js.map +1 -1
  107. package/dist/gs/reflect/types.js.map +1 -1
  108. package/dist/gs/reflect/visiblefields.js.map +1 -1
  109. package/dist/gs/runtime/runtime.js.map +1 -1
  110. package/dist/gs/sort/sort.gs.js.map +1 -1
  111. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  112. package/dist/gs/strconv/quote.gs.js.map +1 -1
  113. package/dist/gs/strings/builder.js.map +1 -1
  114. package/dist/gs/strings/reader.js.map +1 -1
  115. package/dist/gs/strings/replace.js.map +1 -1
  116. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  117. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  118. package/dist/gs/sync/sync.d.ts +1 -0
  119. package/dist/gs/sync/sync.js +12 -0
  120. package/dist/gs/sync/sync.js.map +1 -1
  121. package/dist/gs/time/time.js.map +1 -1
  122. package/dist/gs/unicode/unicode.js.map +1 -1
  123. package/go.mod +2 -2
  124. package/gs/builtin/builtin.ts +31 -6
  125. package/gs/builtin/hostio.test.ts +246 -0
  126. package/gs/builtin/hostio.ts +413 -0
  127. package/gs/builtin/index.ts +1 -0
  128. package/gs/builtin/print.test.ts +48 -0
  129. package/gs/builtin/print.ts +154 -0
  130. package/gs/builtin/runtime-contract.test.ts +230 -0
  131. package/gs/builtin/type.ts +84 -1
  132. package/gs/crypto/rand/index.test.ts +32 -0
  133. package/gs/crypto/rand/index.ts +90 -0
  134. package/gs/crypto/rand/meta.json +5 -0
  135. package/gs/encoding/json/index.test.ts +65 -0
  136. package/gs/encoding/json/index.ts +186 -0
  137. package/gs/fmt/fmt.test.ts +41 -30
  138. package/gs/fmt/fmt.ts +2 -22
  139. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  140. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  141. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  142. package/gs/go/scanner/index.test.ts +50 -0
  143. package/gs/go/scanner/index.ts +157 -0
  144. package/gs/go/token/index.test.ts +21 -0
  145. package/gs/go/token/index.ts +120 -0
  146. package/gs/os/file_unix_js.test.ts +103 -0
  147. package/gs/os/meta.json +1 -2
  148. package/gs/os/tempfile.gs.test.ts +85 -0
  149. package/gs/os/tempfile.gs.ts +71 -11
  150. package/gs/os/types_js.gs.ts +74 -153
  151. package/gs/reflect/index.ts +1 -1
  152. package/gs/reflect/type.ts +106 -17
  153. package/gs/reflect/typefor.test.ts +75 -0
  154. package/gs/sync/sync.test.ts +24 -0
  155. package/gs/sync/sync.ts +12 -0
  156. package/package.json +13 -13
  157. package/compiler/analysis.go +0 -3475
  158. package/compiler/analysis_test.go +0 -338
  159. package/compiler/assignment.go +0 -580
  160. package/compiler/builtin_test.go +0 -92
  161. package/compiler/code-writer.go +0 -115
  162. package/compiler/compiler_test.go +0 -149
  163. package/compiler/composite-lit.go +0 -779
  164. package/compiler/config_test.go +0 -62
  165. package/compiler/constraint.go +0 -86
  166. package/compiler/decl.go +0 -801
  167. package/compiler/expr-call-async.go +0 -188
  168. package/compiler/expr-call-builtins.go +0 -208
  169. package/compiler/expr-call-helpers.go +0 -382
  170. package/compiler/expr-call-make.go +0 -318
  171. package/compiler/expr-call-type-conversion.go +0 -520
  172. package/compiler/expr-call.go +0 -413
  173. package/compiler/expr-selector.go +0 -343
  174. package/compiler/expr-star.go +0 -82
  175. package/compiler/expr-type.go +0 -442
  176. package/compiler/expr-value.go +0 -89
  177. package/compiler/expr.go +0 -773
  178. package/compiler/field.go +0 -183
  179. package/compiler/gs_dependencies_test.go +0 -298
  180. package/compiler/lit.go +0 -322
  181. package/compiler/output.go +0 -72
  182. package/compiler/primitive.go +0 -149
  183. package/compiler/protobuf.go +0 -697
  184. package/compiler/sanitize.go +0 -100
  185. package/compiler/spec-struct.go +0 -995
  186. package/compiler/spec-value.go +0 -540
  187. package/compiler/spec.go +0 -725
  188. package/compiler/stmt-assign.go +0 -664
  189. package/compiler/stmt-for.go +0 -266
  190. package/compiler/stmt-range.go +0 -475
  191. package/compiler/stmt-select.go +0 -262
  192. package/compiler/stmt-type-switch.go +0 -147
  193. package/compiler/stmt.go +0 -1308
  194. package/compiler/type-assert.go +0 -386
  195. package/compiler/type-info.go +0 -156
  196. package/compiler/type-utils.go +0 -207
  197. package/compiler/type.go +0 -892
package/compiler/expr.go DELETED
@@ -1,773 +0,0 @@
1
- package compiler
2
-
3
- import (
4
- "fmt"
5
- "go/ast"
6
- "go/token"
7
- "go/types"
8
-
9
- "github.com/pkg/errors"
10
- )
11
-
12
- // WriteIndexExpr translates a Go index expression (a[b]) to its TypeScript equivalent.
13
- func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
14
- // Check if this might be a generic function instantiation with a single type argument
15
- // In this case, the Index should be a type expression, not a value expression
16
- if tv, ok := c.pkg.TypesInfo.Types[exp.X]; ok {
17
- // If X is a function type, this might be generic instantiation
18
- if _, isFuncType := tv.Type.Underlying().(*types.Signature); isFuncType {
19
- // Check if the index is a type expression (identifier that refers to a type)
20
- if indexIdent, isIdent := exp.Index.(*ast.Ident); isIdent {
21
- // Check if this identifier refers to a type
22
- if obj := c.objectOfIdent(indexIdent); obj != nil {
23
- if _, isTypeName := obj.(*types.TypeName); isTypeName {
24
- // This is a generic function instantiation: f[T] -> f<T>
25
- if err := c.WriteValueExpr(exp.X); err != nil {
26
- return err
27
- }
28
- c.tsw.WriteLiterally("<")
29
- c.WriteTypeExpr(exp.Index)
30
- c.tsw.WriteLiterally(">")
31
- return nil
32
- }
33
- }
34
- }
35
- }
36
- }
37
-
38
- // Handle map access: use Map.get() instead of brackets for reading values
39
- if tv, ok := c.pkg.TypesInfo.Types[exp.X]; ok {
40
- underlyingType := tv.Type.Underlying()
41
- // Check if it's a map type
42
- if mapType, isMap := underlyingType.(*types.Map); isMap {
43
- c.tsw.WriteLiterally("$.mapGet(")
44
- if err := c.WriteValueExpr(exp.X); err != nil {
45
- return err
46
- }
47
- c.tsw.WriteLiterally(", ")
48
- if err := c.WriteValueExpr(exp.Index); err != nil {
49
- return err
50
- }
51
- c.tsw.WriteLiterally(", ")
52
-
53
- // Generate the zero value as the default value for mapGet
54
- c.WriteZeroValueForType(mapType.Elem())
55
- c.tsw.WriteLiterally(")[0]") // Extract the value from the tuple
56
- return nil
57
- }
58
-
59
- // Check if it's a string type
60
- if c.isStringType(tv.Type) {
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 typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
75
- // Check if the type parameter is constrained to slice types
76
- constraint := typeParam.Constraint()
77
- if constraint != nil {
78
- underlying := constraint.Underlying()
79
- if iface, isInterface := underlying.(*types.Interface); isInterface {
80
- // Check if this is a map constraint (like ~map[K]V)
81
- if hasMapConstraint(iface) {
82
- // This is a map type parameter, use map access
83
- c.tsw.WriteLiterally("$.mapGet(")
84
- if err := c.WriteValueExpr(exp.X); err != nil {
85
- return err
86
- }
87
- c.tsw.WriteLiterally(", ")
88
- if err := c.WriteValueExpr(exp.Index); err != nil {
89
- return err
90
- }
91
- c.tsw.WriteLiterally(", ")
92
-
93
- // Generate the zero value as the default value for mapGet
94
- // For type parameters, we need to get the value type from the constraint
95
- mapValueType := getMapValueTypeFromConstraint(iface)
96
- if mapValueType != nil {
97
- c.WriteZeroValueForType(mapValueType)
98
- } else {
99
- c.tsw.WriteLiterally("null")
100
- }
101
- c.tsw.WriteLiterally(")[0]") // Extract the value from the tuple
102
- return nil
103
- }
104
-
105
- // Check if this is a mixed string/byte constraint (like string | []byte)
106
- if hasMixedStringByteConstraint(iface) {
107
- // For mixed constraints, use specialized function that returns number (byte value)
108
- c.tsw.WriteLiterally("$.indexStringOrBytes(")
109
- if err := c.WriteValueExpr(exp.X); err != nil {
110
- return err
111
- }
112
- c.tsw.WriteLiterally(", ")
113
- if err := c.WriteValueExpr(exp.Index); err != nil {
114
- return err
115
- }
116
- c.tsw.WriteLiterally(")")
117
- return nil
118
- }
119
-
120
- // Check if the constraint includes only slice types (pure slice constraint)
121
- if hasSliceConstraint(iface) {
122
- // This is a pure slice type parameter, use regular slice indexing
123
- if err := c.WriteValueExpr(exp.X); err != nil {
124
- return err
125
- }
126
- c.tsw.WriteLiterally("![") // non-null assertion
127
- if err := c.WriteValueExpr(exp.Index); err != nil {
128
- return err
129
- }
130
- c.tsw.WriteLiterally("]")
131
- return nil
132
- }
133
- }
134
- }
135
-
136
- // For other type parameters, use specialized function as fallback
137
- // that returns number (byte value) for better TypeScript typing
138
- c.tsw.WriteLiterally("$.indexStringOrBytes(")
139
- if err := c.WriteValueExpr(exp.X); err != nil {
140
- return err
141
- }
142
- c.tsw.WriteLiterally(", ")
143
- if err := c.WriteValueExpr(exp.Index); err != nil {
144
- return err
145
- }
146
- c.tsw.WriteLiterally(")")
147
- return nil
148
- }
149
- }
150
-
151
- // Regular array/slice access: use brackets
152
- if err := c.WriteValueExpr(exp.X); err != nil {
153
- return err
154
- }
155
- c.tsw.WriteLiterally("![") // non-null assertion
156
- if err := c.WriteValueExpr(exp.Index); err != nil {
157
- return err
158
- }
159
- c.tsw.WriteLiterally("]")
160
- return nil
161
- }
162
-
163
- // WriteIndexListExpr translates a Go generic function instantiation (f[T1, T2]) to its TypeScript equivalent (f<T1, T2>).
164
- func (c *GoToTSCompiler) WriteIndexListExpr(exp *ast.IndexListExpr) error {
165
- // Write the function expression
166
- if err := c.WriteValueExpr(exp.X); err != nil {
167
- return err
168
- }
169
-
170
- // Write the type arguments using TypeScript syntax
171
- c.tsw.WriteLiterally("<")
172
- for i, typeArg := range exp.Indices {
173
- if i > 0 {
174
- c.tsw.WriteLiterally(", ")
175
- }
176
- c.WriteTypeExpr(typeArg)
177
- }
178
- c.tsw.WriteLiterally(">")
179
-
180
- return nil
181
- }
182
-
183
- // WriteTypeAssertExpr translates a Go type assertion expression (e.g., `x.(T)`)
184
- // into a TypeScript call to `$.typeAssert<T_ts>(x_ts, 'TypeName').value`.
185
- // The `$.typeAssert` runtime function handles the actual type check and panic
186
- // if the assertion fails. The `.value` access is used because in an expression
187
- // context, we expect the asserted value directly. The `TypeName` string is used
188
- // by the runtime for error messages.
189
- func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
190
- // Generate a call to $.typeAssert
191
- c.tsw.WriteLiterally("$.mustTypeAssert<")
192
- c.WriteTypeExpr(exp.Type) // Write the asserted type for the generic
193
- c.tsw.WriteLiterally(">(")
194
- if err := c.WriteValueExpr(exp.X); err != nil { // The interface expression
195
- return fmt.Errorf("failed to write interface expression in type assertion expression: %w", err)
196
- }
197
- c.tsw.WriteLiterally(", ")
198
-
199
- // Unwrap parenthesized expressions to handle cases like r.((<-chan T))
200
- typeExpr := exp.Type
201
- for {
202
- if parenExpr, ok := typeExpr.(*ast.ParenExpr); ok {
203
- typeExpr = parenExpr.X
204
- } else {
205
- break
206
- }
207
- }
208
-
209
- c.writeTypeDescription(typeExpr)
210
-
211
- c.tsw.WriteLiterally(")")
212
-
213
- return nil
214
- }
215
-
216
- // isPointerComparison checks if a binary expression `exp` involves comparing
217
- // two pointer types. It uses `go/types` information to determine the types
218
- // of the left (X) and right (Y) operands of the binary expression.
219
- // Returns `true` if both operands are determined to be pointer types,
220
- // `false` otherwise. This is used to apply specific comparison semantics
221
- // for pointers (e.g., comparing the varRef objects directly).
222
- func (c *GoToTSCompiler) isPointerComparison(exp *ast.BinaryExpr) bool {
223
- leftType := c.pkg.TypesInfo.TypeOf(exp.X)
224
- rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
225
- if leftType != nil && rightType != nil {
226
- if _, leftIsPtr := leftType.(*types.Pointer); leftIsPtr {
227
- if _, rightIsPtr := rightType.(*types.Pointer); rightIsPtr {
228
- return true
229
- }
230
- }
231
- }
232
- return false
233
- }
234
-
235
- // getTypeNameString returns a string representation of a Go type expression (`ast.Expr`).
236
- // It handles simple identifiers (e.g., `MyType`) and selector expressions
237
- // (e.g., `pkg.Type`). For more complex or unrecognized type expressions,
238
- // it returns "unknown". This string is primarily used for runtime error messages,
239
- // such as in type assertions.
240
- func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
241
- switch t := typeExpr.(type) {
242
- case *ast.Ident:
243
- return t.Name
244
- case *ast.SelectorExpr:
245
- // For imported types like pkg.Type
246
- if ident, ok := t.X.(*ast.Ident); ok {
247
- return fmt.Sprintf("%s.%s", ident.Name, t.Sel.Name)
248
- }
249
- }
250
- // Default case, use a placeholder for complex types
251
- return "unknown"
252
- }
253
-
254
- // WriteBinaryExpr translates a Go binary expression (`ast.BinaryExpr`) into its
255
- // TypeScript equivalent.
256
- // It handles several cases:
257
- // - Channel send (`ch <- val`): Becomes `await ch.send(val)`.
258
- // - Nil comparison for pointers (`ptr == nil` or `ptr != nil`): Compares the
259
- // pointer (which may be a varRef object or `null`) directly to `null` using
260
- // the translated operator (`==` or `!=`).
261
- // - Pointer comparison (non-nil, `ptr1 == ptr2` or `ptr1 != ptr2`): Compares
262
- // the varRef objects directly using strict equality (`===` or `!==`).
263
- // - Bitwise operations (`&`, `|`, `^`, `<<`, `>>`, `&^`): The expression is wrapped
264
- // in parentheses `()` to ensure correct precedence in TypeScript, and operators
265
- // are mapped (e.g., `&^` might need special handling or is mapped to a runtime helper).
266
- // - Other binary operations (arithmetic, logical, comparison): Operands are
267
- // translated using `WriteValueExpr`, and the operator is mapped to its TypeScript
268
- // equivalent using `TokenToTs`.
269
- //
270
- // Unhandled operators result in a comment and a placeholder.
271
- func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
272
- // Handle special cases like channel send
273
- if exp.Op == token.ARROW {
274
- // Channel send: ch <- val becomes await $.chanSend(ch, val)
275
- c.tsw.WriteLiterally("await $.chanSend(")
276
- if err := c.WriteValueExpr(exp.X); err != nil {
277
- return fmt.Errorf("failed to write channel send target: %w", err)
278
- }
279
- c.tsw.WriteLiterally(", ")
280
- if err := c.WriteValueExpr(exp.Y); err != nil {
281
- return fmt.Errorf("failed to write channel send value: %w", err)
282
- }
283
- c.tsw.WriteLiterally(")")
284
- return nil
285
- }
286
-
287
- // Check if this is a nil comparison for a pointer
288
- isNilComparison := false
289
- var ptrExpr ast.Expr
290
- if (exp.Op == token.EQL || exp.Op == token.NEQ) && c.pkg != nil && c.pkg.TypesInfo != nil {
291
- if leftIdent, ok := exp.Y.(*ast.Ident); ok && leftIdent.Name == "nil" {
292
- leftType := c.pkg.TypesInfo.TypeOf(exp.X)
293
- if _, isPtr := leftType.(*types.Pointer); isPtr {
294
- isNilComparison = true
295
- ptrExpr = exp.X
296
- }
297
- } else if rightIdent, ok := exp.X.(*ast.Ident); ok && rightIdent.Name == "nil" {
298
- rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
299
- if _, isPtr := rightType.(*types.Pointer); isPtr {
300
- isNilComparison = true
301
- ptrExpr = exp.Y
302
- }
303
- }
304
- }
305
-
306
- if isNilComparison {
307
- // For nil comparisons, we need to decide whether to write .value or not
308
- // If the pointer variable is varrefed, we need to access .value
309
- if ident, ok := ptrExpr.(*ast.Ident); ok {
310
- if obj := c.pkg.TypesInfo.ObjectOf(ident); obj != nil {
311
- if c.analysis.NeedsVarRef(obj) {
312
- // Variable is varrefed, so we need to access .value
313
- c.WriteIdent(ident, true) // This will add .value
314
- } else {
315
- // Variable is not varrefed, write directly
316
- c.WriteIdent(ident, false)
317
- }
318
- } else {
319
- // No object info, write directly
320
- c.WriteIdent(ident, false)
321
- }
322
- } else {
323
- // For other expressions, use WriteValueExpr (but this might need review)
324
- if err := c.WriteValueExpr(ptrExpr); err != nil {
325
- return fmt.Errorf("failed to write pointer expression in nil comparison: %w", err)
326
- }
327
- }
328
- c.tsw.WriteLiterally(" ")
329
- tokStr, ok := TokenToTs(exp.Op)
330
- if !ok {
331
- return errors.Errorf("unhandled binary op: %s", exp.Op.String())
332
- }
333
- c.tsw.WriteLiterally(tokStr)
334
- c.tsw.WriteLiterally(" null")
335
- return nil
336
- }
337
-
338
- // Check if this is a pointer comparison (non-nil)
339
- // Compare the varRef objects directly using === or !==
340
- if c.isPointerComparison(exp) {
341
- c.tsw.WriteLiterally("(") // Wrap comparison
342
-
343
- // For pointer comparisons, we need to handle variable varref status
344
- // If a variable is varref'd, we need its .value to get the actual pointer value
345
-
346
- // Check if operands are varref'd variables
347
- var leftObj, rightObj types.Object
348
- leftIsVarRef := false
349
- rightIsVarRef := false
350
-
351
- if leftIdent, ok := exp.X.(*ast.Ident); ok {
352
- leftObj = c.pkg.TypesInfo.ObjectOf(leftIdent)
353
- if leftObj != nil {
354
- leftIsVarRef = c.analysis.NeedsVarRef(leftObj)
355
- }
356
- }
357
-
358
- if rightIdent, ok := exp.Y.(*ast.Ident); ok {
359
- rightObj = c.pkg.TypesInfo.ObjectOf(rightIdent)
360
- if rightObj != nil {
361
- rightIsVarRef = c.analysis.NeedsVarRef(rightObj)
362
- }
363
- }
364
-
365
- // Write left operand
366
- if leftIdent, ok := exp.X.(*ast.Ident); ok {
367
- if leftIsVarRef {
368
- // Variable is varref'd, access its .value to get the pointer
369
- c.WriteIdent(leftIdent, true)
370
- } else {
371
- // Variable is not varref'd, use it directly
372
- c.WriteIdent(leftIdent, false)
373
- }
374
- } else {
375
- // For non-identifiers, use WriteValueExpr
376
- if err := c.WriteValueExpr(exp.X); err != nil {
377
- return fmt.Errorf("failed to write binary expression left operand: %w", err)
378
- }
379
- }
380
-
381
- c.tsw.WriteLiterally(" ")
382
- // Use === for == and !== for !=
383
- tokStr := ""
384
- switch exp.Op {
385
- case token.EQL:
386
- tokStr = "==="
387
- case token.NEQ:
388
- tokStr = "!=="
389
- default:
390
- return errors.Errorf("unhandled pointer comparison op: %s", exp.Op.String())
391
- }
392
- c.tsw.WriteLiterally(tokStr)
393
- c.tsw.WriteLiterally(" ")
394
-
395
- // Write right operand
396
- if rightIdent, ok := exp.Y.(*ast.Ident); ok {
397
- if rightIsVarRef {
398
- // Variable is varref'd, access its .value to get the pointer
399
- c.WriteIdent(rightIdent, true)
400
- } else {
401
- // Variable is not varref'd, use it directly
402
- c.WriteIdent(rightIdent, false)
403
- }
404
- } else {
405
- // For non-identifiers, use WriteValueExpr
406
- if err := c.WriteValueExpr(exp.Y); err != nil {
407
- return fmt.Errorf("failed to write binary expression right operand: %w", err)
408
- }
409
- }
410
-
411
- c.tsw.WriteLiterally(")") // Close wrap
412
- return nil
413
- }
414
-
415
- // Check if the operator is a bitwise operator
416
- isBitwise := false
417
- switch exp.Op {
418
- case token.AND, token.OR, token.XOR, token.SHL, token.SHR, token.AND_NOT:
419
- isBitwise = true
420
- }
421
-
422
- // Handle large bit shift expressions that would overflow in JavaScript
423
- if exp.Op == token.SHL {
424
- // Check if this is 1 << 63 pattern using constant evaluation
425
- leftValue := c.evaluateConstantExpr(exp.X)
426
- rightValue := c.evaluateConstantExpr(exp.Y)
427
-
428
- if leftValue != nil && rightValue != nil {
429
- if leftInt, leftOk := leftValue.(int); leftOk && leftInt == 1 {
430
- if rightInt, rightOk := rightValue.(int); rightOk && rightInt == 63 {
431
- // Replace 1 << 63 with Number.MAX_SAFE_INTEGER (9007199254740991)
432
- // This is the largest integer that can be exactly represented in JavaScript
433
- c.tsw.WriteLiterally("Number.MAX_SAFE_INTEGER")
434
- return nil
435
- }
436
- }
437
- }
438
- }
439
-
440
- if isBitwise {
441
- c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
442
- }
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
- }
498
- if err := c.WriteValueExpr(exp.X); err != nil {
499
- return fmt.Errorf("failed to write binary expression left operand: %w", err)
500
- }
501
- if needsNumberCast {
502
- c.tsw.WriteLiterally(")")
503
- }
504
-
505
- c.tsw.WriteLiterally(" ")
506
- tokStr, ok := TokenToTs(exp.Op)
507
- if !ok {
508
- return errors.Errorf("unhandled binary op: %s", exp.Op.String())
509
- }
510
-
511
- c.tsw.WriteLiterally(tokStr)
512
- c.tsw.WriteLiterally(" ")
513
-
514
- if err := c.WriteValueExpr(exp.Y); err != nil {
515
- return fmt.Errorf("failed to write binary expression right operand: %w", err)
516
- }
517
-
518
- if isIntegerDivision {
519
- c.tsw.WriteLiterally(")") // Close Math.trunc()
520
- }
521
-
522
- if isBitwise {
523
- c.tsw.WriteLiterally(")") // Add closing parenthesis for bitwise operations
524
- }
525
-
526
- return nil
527
- }
528
-
529
- // WriteUnaryExpr translates a Go unary expression (`ast.UnaryExpr`) into its
530
- // TypeScript equivalent.
531
- // It handles several unary operations:
532
- // - Channel receive (`<-ch`): Becomes `await ch.receive()`.
533
- // - Address-of (`&var`):
534
- // - If `var` is a varrefed variable (its address was taken), `&var` evaluates
535
- // to the varRef itself (i.e., `varName` in TypeScript, which holds the varRef).
536
- // - Otherwise (e.g., `&unvarrefedVar`, `&MyStruct{}`, `&FuncCall()`), it evaluates
537
- // the operand `var`. The resulting TypeScript value (e.g., a new object instance)
538
- // acts as the "pointer". VarRefing decisions for such pointers are handled at
539
- // the assignment site.
540
- // - Other unary operators (`+`, `-`, `!`, `^`): Mapped to their TypeScript
541
- // equivalents (e.g., `+`, `-`, `!`, `~` for bitwise NOT). Parentheses are added
542
- // around the operand if it's a binary or unary expression to maintain precedence.
543
- //
544
- // Unhandled operators result in a comment and an attempt to write the operator
545
- // token directly. Postfix operators (`++`, `--`) are expected to be handled by
546
- // their statement contexts (e.g., `IncDecStmt`).
547
- func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
548
- if exp.Op == token.ARROW {
549
- // Channel receive: <-ch becomes await $.chanRecv(ch)
550
- c.tsw.WriteLiterally("await $.chanRecv(")
551
- if err := c.WriteValueExpr(exp.X); err != nil {
552
- return fmt.Errorf("failed to write channel receive operand: %w", err)
553
- }
554
- c.tsw.WriteLiterally(")")
555
- return nil
556
- }
557
-
558
- if exp.Op == token.AND { // Address-of operator (&)
559
- // If the operand is an identifier for a variable that is varrefed,
560
- // the result of & is the varRef itself.
561
- if ident, ok := exp.X.(*ast.Ident); ok {
562
- var obj types.Object
563
- obj = c.pkg.TypesInfo.Uses[ident]
564
- if obj == nil {
565
- obj = c.pkg.TypesInfo.Defs[ident]
566
- }
567
- if obj != nil && c.analysis.NeedsVarRef(obj) {
568
- // &varRefVar -> varRefVar (the variable reference itself)
569
- c.tsw.WriteLiterally(c.sanitizeIdentifier(ident.Name)) // Write the identifier name (which holds the variable reference)
570
- return nil
571
- }
572
- }
573
-
574
- // Note: With inversion to markAsStructValue, we no longer mark &CompositeLit{}
575
- // since we now mark the CompositeLit{} (struct values) instead of pointers
576
-
577
- // Otherwise (&unvarrefedVar, &CompositeLit{}, &FuncCall(), etc.),
578
- // the address-of operator in Go, when used to create a pointer,
579
- // translates to simply evaluating the operand in TypeScript.
580
- // The resulting value (e.g., a new object instance) acts as the "pointer".
581
- // VarRefing decisions are handled at the assignment site based on the LHS variable.
582
-
583
- // Set context flag to prevent marking composite literals as struct values
584
- c.insideAddressOf = true
585
- err := c.WriteValueExpr(exp.X)
586
- c.insideAddressOf = false
587
-
588
- if err != nil {
589
- return fmt.Errorf("failed to write &-operand: %w", err)
590
- }
591
-
592
- return nil
593
- }
594
-
595
- // Handle other unary operators (+, -, !, ^)
596
- tokStr, ok := TokenToTs(exp.Op)
597
- if !ok {
598
- return errors.Errorf("unhandled unary op: %s", exp.Op.String())
599
- }
600
-
601
- // Special case: In Go, ^ is bitwise NOT when used as unary operator
602
- // In TypeScript, bitwise NOT is ~, not ^
603
- if exp.Op == token.XOR {
604
- tokStr = "~"
605
- }
606
-
607
- c.tsw.WriteLiterally(tokStr)
608
-
609
- // Add space if operator is not postfix (e.g., !)
610
- if exp.Op != token.INC && exp.Op != token.DEC {
611
- // Check if operand needs parentheses (e.g., !(-a))
612
- // Basic check: if operand is binary or unary, add parens
613
- needsParens := false
614
- switch exp.X.(type) {
615
- case *ast.BinaryExpr, *ast.UnaryExpr:
616
- needsParens = true
617
- }
618
- if needsParens {
619
- c.tsw.WriteLiterally("(")
620
- }
621
- if err := c.WriteValueExpr(exp.X); err != nil {
622
- return fmt.Errorf("failed to write unary expression operand: %w", err)
623
- }
624
- if needsParens {
625
- c.tsw.WriteLiterally(")")
626
- }
627
- } else {
628
- // Postfix operators (++, --) - operand written first by caller (e.g., IncDecStmt)
629
- // This function shouldn't be called directly for ++/-- in expression context in valid Go?
630
- // If it is, write the operand.
631
- if err := c.WriteValueExpr(exp.X); err != nil {
632
- return fmt.Errorf("failed to write unary expression operand for postfix op: %w", err)
633
- }
634
- }
635
-
636
- return nil
637
- }
638
-
639
- // WriteSliceExpr translates a Go slice expression (e.g., `s[low:high:max]`) to its TypeScript equivalent.
640
- // If `s` is a string and it's not a 3-index slice, it uses `s.substring(low, high)`.
641
- // If `s` is `[]byte` (Uint8Array) and it's not a 3-index slice, it uses $.goSlice.
642
- // Otherwise, it falls back to the `$.goSlice(s, low, high, max)` runtime helper.
643
- func (c *GoToTSCompiler) WriteSliceExpr(exp *ast.SliceExpr) error {
644
- // Check if the expression being sliced is a string
645
- tv := c.pkg.TypesInfo.TypeOf(exp.X)
646
- isString := false
647
- isTypeParam := false
648
- if tv != nil {
649
- isString = c.isStringType(tv)
650
- if _, isTP := tv.(*types.TypeParam); isTP {
651
- isTypeParam = true
652
- }
653
- }
654
-
655
- // Handle type parameters with union constraints (e.g., string | []byte)
656
- if isTypeParam {
657
- // For type parameters, we need to create a runtime helper that handles both string and []byte
658
- c.tsw.WriteLiterally("$.sliceStringOrBytes(")
659
- if err := c.WriteValueExpr(exp.X); err != nil {
660
- return err
661
- }
662
- c.tsw.WriteLiterally(", ")
663
- if exp.Low != nil {
664
- if err := c.WriteValueExpr(exp.Low); err != nil {
665
- return err
666
- }
667
- } else {
668
- c.tsw.WriteLiterally("undefined")
669
- }
670
- c.tsw.WriteLiterally(", ")
671
- if exp.High != nil {
672
- if err := c.WriteValueExpr(exp.High); err != nil {
673
- return err
674
- }
675
- } else {
676
- c.tsw.WriteLiterally("undefined")
677
- }
678
- if exp.Slice3 {
679
- c.tsw.WriteLiterally(", ")
680
- if exp.Max != nil {
681
- if err := c.WriteValueExpr(exp.Max); err != nil {
682
- return err
683
- }
684
- } else {
685
- c.tsw.WriteLiterally("undefined")
686
- }
687
- }
688
- c.tsw.WriteLiterally(")")
689
- return nil
690
- }
691
-
692
- if isString && !exp.Slice3 {
693
- // Use $.sliceString for byte-correct string slicing
694
- c.tsw.WriteLiterally("$.sliceString(")
695
- if err := c.WriteValueExpr(exp.X); err != nil {
696
- return err
697
- }
698
- c.tsw.WriteLiterally(", ")
699
- if exp.Low != nil {
700
- if err := c.WriteValueExpr(exp.Low); err != nil {
701
- return err
702
- }
703
- } else {
704
- // Go's default low for string[:high] is 0.
705
- // $.sliceString can handle undefined for low as 0.
706
- c.tsw.WriteLiterally("undefined")
707
- }
708
- c.tsw.WriteLiterally(", ")
709
- if exp.High != nil {
710
- if err := c.WriteValueExpr(exp.High); err != nil {
711
- return err
712
- }
713
- } else {
714
- // Go's default high for string[low:] means to the end.
715
- // $.sliceString can handle undefined for high as end of string.
716
- c.tsw.WriteLiterally("undefined")
717
- }
718
- c.tsw.WriteLiterally(")")
719
- } else {
720
- // Fallback to $.goSlice for actual slices (arrays) or 3-index string slices (which are rare and might need $.goSlice's complexity)
721
- // Or if it's a string but has Slice3, it's not handled by simple substring.
722
- c.tsw.WriteLiterally("$.goSlice(")
723
- if err := c.WriteValueExpr(exp.X); err != nil {
724
- return err
725
- }
726
- c.tsw.WriteLiterally(", ")
727
- if exp.Low != nil {
728
- if err := c.WriteValueExpr(exp.Low); err != nil {
729
- return err
730
- }
731
- } else {
732
- c.tsw.WriteLiterally("undefined")
733
- }
734
- c.tsw.WriteLiterally(", ")
735
- if exp.High != nil {
736
- if err := c.WriteValueExpr(exp.High); err != nil {
737
- return err
738
- }
739
- } else {
740
- c.tsw.WriteLiterally("undefined")
741
- }
742
- if exp.Slice3 {
743
- c.tsw.WriteLiterally(", ")
744
- if exp.Max != nil {
745
- if err := c.WriteValueExpr(exp.Max); err != nil {
746
- return err
747
- }
748
- } else {
749
- c.tsw.WriteLiterally("undefined")
750
- }
751
- }
752
- c.tsw.WriteLiterally(")")
753
- }
754
- return nil
755
- }
756
-
757
- // WriteKeyValueExpr translates a Go key-value pair expression (`ast.KeyValueExpr`),
758
- // typically found within composite literals (for structs, maps, or arrays with
759
- // indexed elements), into its TypeScript object property equivalent: `key: value`.
760
- // Both the key and the value expressions are recursively translated using
761
- // `WriteValueExpr`. The original Go casing for keys is preserved.
762
- // For example, `MyField: 123` in Go becomes `MyField: 123` in TypeScript.
763
- func (c *GoToTSCompiler) WriteKeyValueExpr(exp *ast.KeyValueExpr) error {
764
- // Keep original Go casing for keys
765
- if err := c.WriteValueExpr(exp.Key); err != nil {
766
- return fmt.Errorf("failed to write key-value expression key: %w", err)
767
- }
768
- c.tsw.WriteLiterally(": ")
769
- if err := c.WriteValueExpr(exp.Value); err != nil {
770
- return fmt.Errorf("failed to write key-value expression value: %w", err)
771
- }
772
- return nil
773
- }