goscript 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/goscript/cmd_compile.go +2 -2
- package/compiler/analysis.go +229 -51
- package/compiler/assignment.go +412 -0
- package/compiler/compiler.go +185 -5885
- package/compiler/compiler_test.go +40 -8
- package/compiler/composite-lit.go +552 -0
- package/compiler/config.go +3 -0
- package/compiler/decl.go +259 -0
- package/compiler/expr-call.go +479 -0
- package/compiler/expr-selector.go +125 -0
- package/compiler/expr-star.go +90 -0
- package/compiler/expr-type.go +309 -0
- package/compiler/expr-value.go +89 -0
- package/compiler/expr.go +591 -0
- package/compiler/field.go +169 -0
- package/compiler/lit.go +131 -0
- package/compiler/primitive.go +148 -0
- package/compiler/{write-type-spec.go → spec-struct.go} +211 -204
- package/compiler/spec-value.go +226 -0
- package/compiler/spec.go +272 -0
- package/compiler/stmt-assign.go +439 -0
- package/compiler/stmt-for.go +178 -0
- package/compiler/stmt-range.go +235 -0
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +792 -0
- package/compiler/type-assert.go +209 -0
- package/compiler/type-info.go +141 -0
- package/compiler/type.go +618 -0
- package/go.mod +2 -1
- package/go.sum +4 -2
- package/package.json +6 -6
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2114
- package/dist/builtin/builtin.d.ts +0 -495
- package/dist/builtin/builtin.js +0 -1490
- package/dist/builtin/builtin.js.map +0 -1
- /package/compiler/{writer.go → code-writer.go} +0 -0
package/compiler/expr.go
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
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.pkg.TypesInfo.Uses[indexIdent]; obj != nil {
|
|
23
|
+
if _, isTypeName := obj.(*types.TypeName); isTypeName {
|
|
24
|
+
// This is a generic function instantiation: f[T] -> f<T>
|
|
25
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
26
|
+
return err
|
|
27
|
+
}
|
|
28
|
+
c.tsw.WriteLiterally("<")
|
|
29
|
+
c.WriteTypeExpr(exp.Index)
|
|
30
|
+
c.tsw.WriteLiterally(">")
|
|
31
|
+
return nil
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
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(")")
|
|
56
|
+
return nil
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if it's a string type
|
|
60
|
+
if basicType, isBasic := underlyingType.(*types.Basic); isBasic && (basicType.Info()&types.IsString) != 0 {
|
|
61
|
+
c.tsw.WriteLiterally("$.indexString(")
|
|
62
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
63
|
+
return err
|
|
64
|
+
}
|
|
65
|
+
c.tsw.WriteLiterally(", ")
|
|
66
|
+
if err := c.WriteValueExpr(exp.Index); err != nil {
|
|
67
|
+
return err
|
|
68
|
+
}
|
|
69
|
+
c.tsw.WriteLiterally(")")
|
|
70
|
+
return nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if it's a type parameter with a union constraint (e.g., string | []byte)
|
|
74
|
+
if _, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
|
|
75
|
+
// For type parameters with string | []byte constraint, use specialized function
|
|
76
|
+
// that returns number (byte value) for better TypeScript typing
|
|
77
|
+
c.tsw.WriteLiterally("$.indexStringOrBytes(")
|
|
78
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
79
|
+
return err
|
|
80
|
+
}
|
|
81
|
+
c.tsw.WriteLiterally(", ")
|
|
82
|
+
if err := c.WriteValueExpr(exp.Index); err != nil {
|
|
83
|
+
return err
|
|
84
|
+
}
|
|
85
|
+
c.tsw.WriteLiterally(")")
|
|
86
|
+
return nil
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Regular array/slice access: use brackets
|
|
91
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
92
|
+
return err
|
|
93
|
+
}
|
|
94
|
+
c.tsw.WriteLiterally("![") // non-null assertion
|
|
95
|
+
if err := c.WriteValueExpr(exp.Index); err != nil {
|
|
96
|
+
return err
|
|
97
|
+
}
|
|
98
|
+
c.tsw.WriteLiterally("]")
|
|
99
|
+
return nil
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// WriteIndexListExpr translates a Go generic function instantiation (f[T1, T2]) to its TypeScript equivalent (f<T1, T2>).
|
|
103
|
+
func (c *GoToTSCompiler) WriteIndexListExpr(exp *ast.IndexListExpr) error {
|
|
104
|
+
// Write the function expression
|
|
105
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
106
|
+
return err
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Write the type arguments using TypeScript syntax
|
|
110
|
+
c.tsw.WriteLiterally("<")
|
|
111
|
+
for i, typeArg := range exp.Indices {
|
|
112
|
+
if i > 0 {
|
|
113
|
+
c.tsw.WriteLiterally(", ")
|
|
114
|
+
}
|
|
115
|
+
c.WriteTypeExpr(typeArg)
|
|
116
|
+
}
|
|
117
|
+
c.tsw.WriteLiterally(">")
|
|
118
|
+
|
|
119
|
+
return nil
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// WriteTypeAssertExpr translates a Go type assertion expression (e.g., `x.(T)`)
|
|
123
|
+
// into a TypeScript call to `$.typeAssert<T_ts>(x_ts, 'TypeName').value`.
|
|
124
|
+
// The `$.typeAssert` runtime function handles the actual type check and panic
|
|
125
|
+
// if the assertion fails. The `.value` access is used because in an expression
|
|
126
|
+
// context, we expect the asserted value directly. The `TypeName` string is used
|
|
127
|
+
// by the runtime for error messages.
|
|
128
|
+
func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
|
|
129
|
+
// Generate a call to $.typeAssert
|
|
130
|
+
c.tsw.WriteLiterally("$.mustTypeAssert<")
|
|
131
|
+
c.WriteTypeExpr(exp.Type) // Write the asserted type for the generic
|
|
132
|
+
c.tsw.WriteLiterally(">(")
|
|
133
|
+
if err := c.WriteValueExpr(exp.X); err != nil { // The interface expression
|
|
134
|
+
return fmt.Errorf("failed to write interface expression in type assertion expression: %w", err)
|
|
135
|
+
}
|
|
136
|
+
c.tsw.WriteLiterally(", ")
|
|
137
|
+
|
|
138
|
+
// Unwrap parenthesized expressions to handle cases like r.((<-chan T))
|
|
139
|
+
typeExpr := exp.Type
|
|
140
|
+
for {
|
|
141
|
+
if parenExpr, ok := typeExpr.(*ast.ParenExpr); ok {
|
|
142
|
+
typeExpr = parenExpr.X
|
|
143
|
+
} else {
|
|
144
|
+
break
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
c.writeTypeDescription(typeExpr)
|
|
149
|
+
|
|
150
|
+
c.tsw.WriteLiterally(")")
|
|
151
|
+
|
|
152
|
+
return nil
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// isPointerComparison checks if a binary expression `exp` involves comparing
|
|
156
|
+
// two pointer types. It uses `go/types` information to determine the types
|
|
157
|
+
// of the left (X) and right (Y) operands of the binary expression.
|
|
158
|
+
// Returns `true` if both operands are determined to be pointer types,
|
|
159
|
+
// `false` otherwise. This is used to apply specific comparison semantics
|
|
160
|
+
// for pointers (e.g., comparing the box objects directly).
|
|
161
|
+
func (c *GoToTSCompiler) isPointerComparison(exp *ast.BinaryExpr) bool {
|
|
162
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
163
|
+
rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
|
|
164
|
+
if leftType != nil && rightType != nil {
|
|
165
|
+
if _, leftIsPtr := leftType.(*types.Pointer); leftIsPtr {
|
|
166
|
+
if _, rightIsPtr := rightType.(*types.Pointer); rightIsPtr {
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// getTypeNameString returns a string representation of a Go type expression (`ast.Expr`).
|
|
175
|
+
// It handles simple identifiers (e.g., `MyType`) and selector expressions
|
|
176
|
+
// (e.g., `pkg.Type`). For more complex or unrecognized type expressions,
|
|
177
|
+
// it returns "unknown". This string is primarily used for runtime error messages,
|
|
178
|
+
// such as in type assertions.
|
|
179
|
+
func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
|
|
180
|
+
switch t := typeExpr.(type) {
|
|
181
|
+
case *ast.Ident:
|
|
182
|
+
return t.Name
|
|
183
|
+
case *ast.SelectorExpr:
|
|
184
|
+
// For imported types like pkg.Type
|
|
185
|
+
if ident, ok := t.X.(*ast.Ident); ok {
|
|
186
|
+
return fmt.Sprintf("%s.%s", ident.Name, t.Sel.Name)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Default case, use a placeholder for complex types
|
|
190
|
+
return "unknown"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// WriteBinaryExpr translates a Go binary expression (`ast.BinaryExpr`) into its
|
|
194
|
+
// TypeScript equivalent.
|
|
195
|
+
// It handles several cases:
|
|
196
|
+
// - Channel send (`ch <- val`): Becomes `await ch.send(val)`.
|
|
197
|
+
// - Nil comparison for pointers (`ptr == nil` or `ptr != nil`): Compares the
|
|
198
|
+
// pointer (which may be a box object or `null`) directly to `null` using
|
|
199
|
+
// the translated operator (`==` or `!=`).
|
|
200
|
+
// - Pointer comparison (non-nil, `ptr1 == ptr2` or `ptr1 != ptr2`): Compares
|
|
201
|
+
// the box objects directly using strict equality (`===` or `!==`).
|
|
202
|
+
// - Bitwise operations (`&`, `|`, `^`, `<<`, `>>`, `&^`): The expression is wrapped
|
|
203
|
+
// in parentheses `()` to ensure correct precedence in TypeScript, and operators
|
|
204
|
+
// are mapped (e.g., `&^` might need special handling or is mapped to a runtime helper).
|
|
205
|
+
// - Other binary operations (arithmetic, logical, comparison): Operands are
|
|
206
|
+
// translated using `WriteValueExpr`, and the operator is mapped to its TypeScript
|
|
207
|
+
// equivalent using `TokenToTs`.
|
|
208
|
+
//
|
|
209
|
+
// Unhandled operators result in a comment and a placeholder.
|
|
210
|
+
func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
211
|
+
// Handle special cases like channel send
|
|
212
|
+
if exp.Op == token.ARROW {
|
|
213
|
+
// Channel send: ch <- val becomes await $.chanSend(ch, val)
|
|
214
|
+
c.tsw.WriteLiterally("await $.chanSend(")
|
|
215
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
216
|
+
return fmt.Errorf("failed to write channel send target: %w", err)
|
|
217
|
+
}
|
|
218
|
+
c.tsw.WriteLiterally(", ")
|
|
219
|
+
if err := c.WriteValueExpr(exp.Y); err != nil {
|
|
220
|
+
return fmt.Errorf("failed to write channel send value: %w", err)
|
|
221
|
+
}
|
|
222
|
+
c.tsw.WriteLiterally(")")
|
|
223
|
+
return nil
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check if this is a nil comparison for a pointer
|
|
227
|
+
isNilComparison := false
|
|
228
|
+
var ptrExpr ast.Expr
|
|
229
|
+
if (exp.Op == token.EQL || exp.Op == token.NEQ) && c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
230
|
+
if leftIdent, ok := exp.Y.(*ast.Ident); ok && leftIdent.Name == "nil" {
|
|
231
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
232
|
+
if _, isPtr := leftType.(*types.Pointer); isPtr {
|
|
233
|
+
isNilComparison = true
|
|
234
|
+
ptrExpr = exp.X
|
|
235
|
+
}
|
|
236
|
+
} else if rightIdent, ok := exp.X.(*ast.Ident); ok && rightIdent.Name == "nil" {
|
|
237
|
+
rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
|
|
238
|
+
if _, isPtr := rightType.(*types.Pointer); isPtr {
|
|
239
|
+
isNilComparison = true
|
|
240
|
+
ptrExpr = exp.Y
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if isNilComparison {
|
|
246
|
+
// Compare the box object directly to null
|
|
247
|
+
if err := c.WriteValueExpr(ptrExpr); err != nil {
|
|
248
|
+
return fmt.Errorf("failed to write pointer expression in nil comparison: %w", err)
|
|
249
|
+
}
|
|
250
|
+
c.tsw.WriteLiterally(" ")
|
|
251
|
+
tokStr, ok := TokenToTs(exp.Op)
|
|
252
|
+
if !ok {
|
|
253
|
+
return errors.Errorf("unhandled binary op: %s", exp.Op.String())
|
|
254
|
+
}
|
|
255
|
+
c.tsw.WriteLiterally(tokStr)
|
|
256
|
+
c.tsw.WriteLiterally(" null")
|
|
257
|
+
return nil
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check if this is a pointer comparison (non-nil)
|
|
261
|
+
// Compare the box objects directly using === or !==
|
|
262
|
+
if c.isPointerComparison(exp) {
|
|
263
|
+
c.tsw.WriteLiterally("(") // Wrap comparison
|
|
264
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
265
|
+
return fmt.Errorf("failed to write binary expression left operand: %w", err)
|
|
266
|
+
}
|
|
267
|
+
c.tsw.WriteLiterally(" ")
|
|
268
|
+
// Use === for == and !== for !=
|
|
269
|
+
tokStr := ""
|
|
270
|
+
switch exp.Op {
|
|
271
|
+
case token.EQL:
|
|
272
|
+
tokStr = "==="
|
|
273
|
+
case token.NEQ:
|
|
274
|
+
tokStr = "!=="
|
|
275
|
+
default:
|
|
276
|
+
return errors.Errorf("unhandled pointer comparison op: %s", exp.Op.String())
|
|
277
|
+
}
|
|
278
|
+
c.tsw.WriteLiterally(tokStr)
|
|
279
|
+
c.tsw.WriteLiterally(" ")
|
|
280
|
+
if err := c.WriteValueExpr(exp.Y); err != nil {
|
|
281
|
+
return fmt.Errorf("failed to write binary expression right operand: %w", err)
|
|
282
|
+
}
|
|
283
|
+
c.tsw.WriteLiterally(")") // Close wrap
|
|
284
|
+
return nil
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check if the operator is a bitwise operator
|
|
288
|
+
isBitwise := false
|
|
289
|
+
switch exp.Op {
|
|
290
|
+
case token.AND, token.OR, token.XOR, token.SHL, token.SHR, token.AND_NOT:
|
|
291
|
+
isBitwise = true
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Special handling for large bit shift expressions that would overflow in JavaScript
|
|
295
|
+
if exp.Op == token.SHL {
|
|
296
|
+
// Check if this is 1 << 63 pattern
|
|
297
|
+
if leftLit, leftIsLit := exp.X.(*ast.BasicLit); leftIsLit && leftLit.Value == "1" {
|
|
298
|
+
if rightLit, rightIsLit := exp.Y.(*ast.BasicLit); rightIsLit && rightLit.Value == "63" {
|
|
299
|
+
// Replace 1 << 63 with Number.MAX_SAFE_INTEGER (9007199254740991)
|
|
300
|
+
// This is the largest integer that can be exactly represented in JavaScript
|
|
301
|
+
c.tsw.WriteLiterally("Number.MAX_SAFE_INTEGER")
|
|
302
|
+
return nil
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if isBitwise {
|
|
308
|
+
c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
312
|
+
return fmt.Errorf("failed to write binary expression left operand: %w", err)
|
|
313
|
+
}
|
|
314
|
+
c.tsw.WriteLiterally(" ")
|
|
315
|
+
tokStr, ok := TokenToTs(exp.Op)
|
|
316
|
+
if !ok {
|
|
317
|
+
return errors.Errorf("unhandled binary op: %s", exp.Op.String())
|
|
318
|
+
}
|
|
319
|
+
c.tsw.WriteLiterally(tokStr)
|
|
320
|
+
c.tsw.WriteLiterally(" ")
|
|
321
|
+
if err := c.WriteValueExpr(exp.Y); err != nil {
|
|
322
|
+
return fmt.Errorf("failed to write binary expression right operand: %w", err)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if isBitwise {
|
|
326
|
+
c.tsw.WriteLiterally(")") // Add closing parenthesis for bitwise operations
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return nil
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// WriteUnaryExpr translates a Go unary expression (`ast.UnaryExpr`) into its
|
|
333
|
+
// TypeScript equivalent.
|
|
334
|
+
// It handles several unary operations:
|
|
335
|
+
// - Channel receive (`<-ch`): Becomes `await ch.receive()`.
|
|
336
|
+
// - Address-of (`&var`):
|
|
337
|
+
// - If `var` is a boxed variable (its address was taken), `&var` evaluates
|
|
338
|
+
// to the box itself (i.e., `varName` in TypeScript, which holds the box).
|
|
339
|
+
// - Otherwise (e.g., `&unboxedVar`, `&MyStruct{}`, `&FuncCall()`), it evaluates
|
|
340
|
+
// the operand `var`. The resulting TypeScript value (e.g., a new object instance)
|
|
341
|
+
// acts as the "pointer". Boxing decisions for such pointers are handled at
|
|
342
|
+
// the assignment site.
|
|
343
|
+
// - Other unary operators (`+`, `-`, `!`, `^`): Mapped to their TypeScript
|
|
344
|
+
// equivalents (e.g., `+`, `-`, `!`, `~` for bitwise NOT). Parentheses are added
|
|
345
|
+
// around the operand if it's a binary or unary expression to maintain precedence.
|
|
346
|
+
//
|
|
347
|
+
// Unhandled operators result in a comment and an attempt to write the operator
|
|
348
|
+
// token directly. Postfix operators (`++`, `--`) are expected to be handled by
|
|
349
|
+
// their statement contexts (e.g., `IncDecStmt`).
|
|
350
|
+
func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
|
|
351
|
+
if exp.Op == token.ARROW {
|
|
352
|
+
// Channel receive: <-ch becomes await $.chanRecv(ch)
|
|
353
|
+
c.tsw.WriteLiterally("await $.chanRecv(")
|
|
354
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
355
|
+
return fmt.Errorf("failed to write channel receive operand: %w", err)
|
|
356
|
+
}
|
|
357
|
+
c.tsw.WriteLiterally(")")
|
|
358
|
+
return nil
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if exp.Op == token.AND { // Address-of operator (&)
|
|
362
|
+
// If the operand is an identifier for a variable that is boxed,
|
|
363
|
+
// the result of & is the box itself.
|
|
364
|
+
if ident, ok := exp.X.(*ast.Ident); ok {
|
|
365
|
+
var obj types.Object
|
|
366
|
+
obj = c.pkg.TypesInfo.Uses[ident]
|
|
367
|
+
if obj == nil {
|
|
368
|
+
obj = c.pkg.TypesInfo.Defs[ident]
|
|
369
|
+
}
|
|
370
|
+
if obj != nil && c.analysis.NeedsBoxed(obj) {
|
|
371
|
+
// &boxedVar -> boxedVar (the box itself)
|
|
372
|
+
c.tsw.WriteLiterally(ident.Name) // Write the identifier name (which holds the box)
|
|
373
|
+
return nil
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Otherwise (&unboxedVar, &CompositeLit{}, &FuncCall(), etc.),
|
|
378
|
+
// the address-of operator in Go, when used to create a pointer,
|
|
379
|
+
// translates to simply evaluating the operand in TypeScript.
|
|
380
|
+
// The resulting value (e.g., a new object instance) acts as the "pointer".
|
|
381
|
+
// Boxing decisions are handled at the assignment site based on the LHS variable.
|
|
382
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
383
|
+
return fmt.Errorf("failed to write &-operand: %w", err)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return nil
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Handle other unary operators (+, -, !, ^)
|
|
390
|
+
tokStr, ok := TokenToTs(exp.Op)
|
|
391
|
+
if !ok {
|
|
392
|
+
return errors.Errorf("unhandled unary op: %s", exp.Op.String())
|
|
393
|
+
}
|
|
394
|
+
c.tsw.WriteLiterally(tokStr)
|
|
395
|
+
|
|
396
|
+
// Add space if operator is not postfix (e.g., !)
|
|
397
|
+
if exp.Op != token.INC && exp.Op != token.DEC {
|
|
398
|
+
// Check if operand needs parentheses (e.g., !(-a))
|
|
399
|
+
// Basic check: if operand is binary or unary, add parens
|
|
400
|
+
needsParens := false
|
|
401
|
+
switch exp.X.(type) {
|
|
402
|
+
case *ast.BinaryExpr, *ast.UnaryExpr:
|
|
403
|
+
needsParens = true
|
|
404
|
+
}
|
|
405
|
+
if needsParens {
|
|
406
|
+
c.tsw.WriteLiterally("(")
|
|
407
|
+
}
|
|
408
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
409
|
+
return fmt.Errorf("failed to write unary expression operand: %w", err)
|
|
410
|
+
}
|
|
411
|
+
if needsParens {
|
|
412
|
+
c.tsw.WriteLiterally(")")
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
// Postfix operators (++, --) - operand written first by caller (e.g., IncDecStmt)
|
|
416
|
+
// This function shouldn't be called directly for ++/-- in expression context in valid Go?
|
|
417
|
+
// If it is, write the operand.
|
|
418
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
419
|
+
return fmt.Errorf("failed to write unary expression operand for postfix op: %w", err)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return nil
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// WriteSliceExpr translates a Go slice expression (e.g., `s[low:high:max]`) to its TypeScript equivalent.
|
|
427
|
+
// If `s` is a string and it's not a 3-index slice, it uses `s.substring(low, high)`.
|
|
428
|
+
// If `s` is `[]byte` (Uint8Array) and it's not a 3-index slice, it uses `s.subarray(low, high)`.
|
|
429
|
+
// Otherwise, it falls back to the `$.goSlice(s, low, high, max)` runtime helper.
|
|
430
|
+
func (c *GoToTSCompiler) WriteSliceExpr(exp *ast.SliceExpr) error {
|
|
431
|
+
// Check if the expression being sliced is a string
|
|
432
|
+
tv := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
433
|
+
isString := false
|
|
434
|
+
isByteSlice := false
|
|
435
|
+
isTypeParam := false
|
|
436
|
+
if tv != nil {
|
|
437
|
+
if basicType, isBasic := tv.Underlying().(*types.Basic); isBasic && (basicType.Info()&types.IsString) != 0 {
|
|
438
|
+
isString = true
|
|
439
|
+
}
|
|
440
|
+
if sliceType, isSlice := tv.Underlying().(*types.Slice); isSlice {
|
|
441
|
+
if basicElem, isBasic := sliceType.Elem().(*types.Basic); isBasic && basicElem.Kind() == types.Uint8 {
|
|
442
|
+
isByteSlice = true
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if _, isTP := tv.(*types.TypeParam); isTP {
|
|
446
|
+
isTypeParam = true
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Handle type parameters with union constraints (e.g., string | []byte)
|
|
451
|
+
if isTypeParam {
|
|
452
|
+
// For type parameters, we need to create a runtime helper that handles both string and []byte
|
|
453
|
+
c.tsw.WriteLiterally("$.sliceStringOrBytes(")
|
|
454
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
455
|
+
return err
|
|
456
|
+
}
|
|
457
|
+
c.tsw.WriteLiterally(", ")
|
|
458
|
+
if exp.Low != nil {
|
|
459
|
+
if err := c.WriteValueExpr(exp.Low); err != nil {
|
|
460
|
+
return err
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
c.tsw.WriteLiterally("undefined")
|
|
464
|
+
}
|
|
465
|
+
c.tsw.WriteLiterally(", ")
|
|
466
|
+
if exp.High != nil {
|
|
467
|
+
if err := c.WriteValueExpr(exp.High); err != nil {
|
|
468
|
+
return err
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
c.tsw.WriteLiterally("undefined")
|
|
472
|
+
}
|
|
473
|
+
if exp.Slice3 {
|
|
474
|
+
c.tsw.WriteLiterally(", ")
|
|
475
|
+
if exp.Max != nil {
|
|
476
|
+
if err := c.WriteValueExpr(exp.Max); err != nil {
|
|
477
|
+
return err
|
|
478
|
+
}
|
|
479
|
+
} else {
|
|
480
|
+
c.tsw.WriteLiterally("undefined")
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
c.tsw.WriteLiterally(")")
|
|
484
|
+
return nil
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if isString && !exp.Slice3 {
|
|
488
|
+
// Use $.sliceString for byte-correct string slicing
|
|
489
|
+
c.tsw.WriteLiterally("$.sliceString(")
|
|
490
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
491
|
+
return err
|
|
492
|
+
}
|
|
493
|
+
c.tsw.WriteLiterally(", ")
|
|
494
|
+
if exp.Low != nil {
|
|
495
|
+
if err := c.WriteValueExpr(exp.Low); err != nil {
|
|
496
|
+
return err
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
// Go's default low for string[:high] is 0.
|
|
500
|
+
// $.sliceString can handle undefined for low as 0.
|
|
501
|
+
c.tsw.WriteLiterally("undefined")
|
|
502
|
+
}
|
|
503
|
+
c.tsw.WriteLiterally(", ")
|
|
504
|
+
if exp.High != nil {
|
|
505
|
+
if err := c.WriteValueExpr(exp.High); err != nil {
|
|
506
|
+
return err
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
// Go's default high for string[low:] means to the end.
|
|
510
|
+
// $.sliceString can handle undefined for high as end of string.
|
|
511
|
+
c.tsw.WriteLiterally("undefined")
|
|
512
|
+
}
|
|
513
|
+
c.tsw.WriteLiterally(")")
|
|
514
|
+
} else if isByteSlice && !exp.Slice3 {
|
|
515
|
+
// Use s.subarray(low, high) for []byte slices
|
|
516
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
517
|
+
return err
|
|
518
|
+
}
|
|
519
|
+
c.tsw.WriteLiterally(".subarray(")
|
|
520
|
+
if exp.Low != nil {
|
|
521
|
+
if err := c.WriteValueExpr(exp.Low); err != nil {
|
|
522
|
+
return err
|
|
523
|
+
}
|
|
524
|
+
} else {
|
|
525
|
+
c.tsw.WriteLiterally("0") // Default low for subarray is 0
|
|
526
|
+
}
|
|
527
|
+
if exp.High != nil {
|
|
528
|
+
c.tsw.WriteLiterally(", ")
|
|
529
|
+
if err := c.WriteValueExpr(exp.High); err != nil {
|
|
530
|
+
return err
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
// If high is omitted, subarray goes to the end of the array.
|
|
534
|
+
// No need to write undefined or length, just close the parenthesis if low was the last arg.
|
|
535
|
+
}
|
|
536
|
+
c.tsw.WriteLiterally(")")
|
|
537
|
+
} else {
|
|
538
|
+
// Fallback to $.goSlice for actual slices (arrays) or 3-index string slices (which are rare and might need $.goSlice's complexity)
|
|
539
|
+
// Or if it's a string but has Slice3, it's not handled by simple substring.
|
|
540
|
+
c.tsw.WriteLiterally("$.goSlice(")
|
|
541
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
542
|
+
return err
|
|
543
|
+
}
|
|
544
|
+
c.tsw.WriteLiterally(", ")
|
|
545
|
+
if exp.Low != nil {
|
|
546
|
+
if err := c.WriteValueExpr(exp.Low); err != nil {
|
|
547
|
+
return err
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
c.tsw.WriteLiterally("undefined")
|
|
551
|
+
}
|
|
552
|
+
c.tsw.WriteLiterally(", ")
|
|
553
|
+
if exp.High != nil {
|
|
554
|
+
if err := c.WriteValueExpr(exp.High); err != nil {
|
|
555
|
+
return err
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
c.tsw.WriteLiterally("undefined")
|
|
559
|
+
}
|
|
560
|
+
if exp.Slice3 {
|
|
561
|
+
c.tsw.WriteLiterally(", ")
|
|
562
|
+
if exp.Max != nil {
|
|
563
|
+
if err := c.WriteValueExpr(exp.Max); err != nil {
|
|
564
|
+
return err
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
c.tsw.WriteLiterally("undefined")
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
c.tsw.WriteLiterally(")")
|
|
571
|
+
}
|
|
572
|
+
return nil
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// WriteKeyValueExpr translates a Go key-value pair expression (`ast.KeyValueExpr`),
|
|
576
|
+
// typically found within composite literals (for structs, maps, or arrays with
|
|
577
|
+
// indexed elements), into its TypeScript object property equivalent: `key: value`.
|
|
578
|
+
// Both the key and the value expressions are recursively translated using
|
|
579
|
+
// `WriteValueExpr`. The original Go casing for keys is preserved.
|
|
580
|
+
// For example, `MyField: 123` in Go becomes `MyField: 123` in TypeScript.
|
|
581
|
+
func (c *GoToTSCompiler) WriteKeyValueExpr(exp *ast.KeyValueExpr) error {
|
|
582
|
+
// Keep original Go casing for keys
|
|
583
|
+
if err := c.WriteValueExpr(exp.Key); err != nil {
|
|
584
|
+
return fmt.Errorf("failed to write key-value expression key: %w", err)
|
|
585
|
+
}
|
|
586
|
+
c.tsw.WriteLiterally(": ")
|
|
587
|
+
if err := c.WriteValueExpr(exp.Value); err != nil {
|
|
588
|
+
return fmt.Errorf("failed to write key-value expression value: %w", err)
|
|
589
|
+
}
|
|
590
|
+
return nil
|
|
591
|
+
}
|