goscript 0.0.21 → 0.0.22

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.
@@ -0,0 +1,407 @@
1
+ package compiler
2
+
3
+ import (
4
+ "go/ast"
5
+ "go/token"
6
+ "go/types"
7
+
8
+ "golang.org/x/tools/go/packages"
9
+ )
10
+
11
+ // writeAssignmentCore handles the central logic for translating Go assignment
12
+ // operations (LHS op RHS) into TypeScript. It's called by `WriteStmtAssign`
13
+ // and other functions that need to generate assignment code.
14
+ //
15
+ // Key behaviors:
16
+ // - Multi-variable assignment (e.g., `a, b = b, a`): Translates to TypeScript
17
+ // array destructuring: `[a_ts, b_ts] = [b_ts, a_ts]`. It correctly handles
18
+ // non-null assertions for array index expressions on both LHS and RHS if
19
+ // all expressions involved are index expressions (common in swaps).
20
+ // - Single-variable assignment to a map index (`myMap[key] = value`): Translates
21
+ // to `$.mapSet(myMap_ts, key_ts, value_ts)`.
22
+ // - Other single-variable assignments (`variable = value`):
23
+ // - The LHS expression is written (caller typically ensures `.value` is appended
24
+ // if assigning to a boxed variable's content).
25
+ // - The Go assignment token (`tok`, e.g., `=`, `+=`) is translated to its
26
+ // TypeScript equivalent using `TokenToTs`.
27
+ // - The RHS expression(s) are written. If `shouldApplyClone` indicates the RHS
28
+ // is a struct value, `.clone()` is appended to the translated RHS to emulate
29
+ // Go's value semantics for struct assignment.
30
+ // - Blank identifiers (`_`) on the LHS are handled by omitting them in TypeScript
31
+ // destructuring patterns or by skipping the assignment for single assignments.
32
+ //
33
+ // This function handles all assignment types including:
34
+ // - Pointer dereference assignments (*p = v)
35
+ // - Blank identifier assignments (_ = v)
36
+ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Token, addDeclaration bool) error {
37
+ // Handle blank identifier (_) on the LHS for single assignments
38
+ if len(lhs) == 1 && len(rhs) == 1 {
39
+ if ident, ok := lhs[0].(*ast.Ident); ok && ident.Name == "_" {
40
+ // Evaluate the RHS expression for side effects, but don't assign it
41
+ c.tsw.WriteLiterally("/* _ = */ ")
42
+ if err := c.WriteValueExpr(rhs[0]); err != nil {
43
+ return err
44
+ }
45
+ return nil
46
+ }
47
+
48
+ // Handle the special case of "*p = val" (assignment to dereferenced pointer)
49
+ if starExpr, ok := lhs[0].(*ast.StarExpr); ok {
50
+ // For *p = val, we need to set p's .value property
51
+ // Write "p!.value = " for the underlying value
52
+ if err := c.WriteValueExpr(starExpr.X); err != nil { // p in *p
53
+ return err
54
+ }
55
+ c.tsw.WriteLiterally("!.value = ") // Add non-null assertion for TS safety
56
+
57
+ // Handle the RHS expression (potentially adding .clone() for structs)
58
+ if shouldApplyClone(c.pkg, rhs[0]) {
59
+ if err := c.WriteValueExpr(rhs[0]); err != nil {
60
+ return err
61
+ }
62
+ c.tsw.WriteLiterally(".clone()")
63
+ } else {
64
+ if err := c.WriteValueExpr(rhs[0]); err != nil {
65
+ return err
66
+ }
67
+ }
68
+ return nil
69
+ }
70
+
71
+ // Special handling for boxed variables in declarations
72
+ if addDeclaration && tok == token.DEFINE {
73
+ // Determine if LHS is boxed
74
+ isLHSBoxed := false
75
+ var lhsIdent *ast.Ident
76
+ var lhsObj types.Object
77
+
78
+ if ident, ok := lhs[0].(*ast.Ident); ok {
79
+ lhsIdent = ident
80
+ // Get the types.Object from the identifier
81
+ if use, ok := c.pkg.TypesInfo.Uses[ident]; ok {
82
+ lhsObj = use
83
+ } else if def, ok := c.pkg.TypesInfo.Defs[ident]; ok {
84
+ lhsObj = def
85
+ }
86
+
87
+ // Check if this variable needs to be boxed
88
+ if lhsObj != nil && c.analysis.NeedsBoxed(lhsObj) {
89
+ isLHSBoxed = true
90
+ }
91
+ }
92
+
93
+ // Special handling for short declaration of boxed variables
94
+ if isLHSBoxed && lhsIdent != nil {
95
+ c.tsw.WriteLiterally("let ")
96
+ // Just write the identifier name without .value
97
+ c.tsw.WriteLiterally(lhsIdent.Name)
98
+
99
+ // Add type annotation for boxed variables in declarations
100
+ if lhsObj != nil {
101
+ c.tsw.WriteLiterally(": ")
102
+ c.tsw.WriteLiterally("$.Box<")
103
+ c.WriteGoType(lhsObj.Type())
104
+ c.tsw.WriteLiterally(">")
105
+ }
106
+
107
+ c.tsw.WriteLiterally(" = ")
108
+
109
+ // Box the initializer
110
+ c.tsw.WriteLiterally("$.box(")
111
+ if err := c.WriteValueExpr(rhs[0]); err != nil {
112
+ return err
113
+ }
114
+ c.tsw.WriteLiterally(")")
115
+ return nil
116
+ }
117
+
118
+ c.tsw.WriteLiterally("let ")
119
+ }
120
+ }
121
+
122
+ // Special case for multi-variable assignment to handle array element swaps
123
+ if len(lhs) > 1 && len(rhs) > 1 {
124
+ // Check if this is an array element swap pattern (common pattern a[i], a[j] = a[j], a[i])
125
+ // Identify if we're dealing with array index expressions that might need null assertions
126
+ allIndexExprs := true
127
+ for _, expr := range append(lhs, rhs...) {
128
+ _, isIndexExpr := expr.(*ast.IndexExpr)
129
+ if !isIndexExpr {
130
+ allIndexExprs = false
131
+ break
132
+ }
133
+ }
134
+
135
+ // Use array destructuring for multi-variable assignments
136
+ c.tsw.WriteLiterally("[")
137
+ for i, l := range lhs {
138
+ if i != 0 {
139
+ c.tsw.WriteLiterally(", ")
140
+ }
141
+
142
+ // Handle blank identifier
143
+ if ident, ok := l.(*ast.Ident); ok && ident.Name == "_" {
144
+ // If it's a blank identifier, we write nothing,
145
+ // leaving an empty slot in the destructuring array.
146
+ } else if indexExpr, ok := l.(*ast.IndexExpr); ok && allIndexExprs { // MODIFICATION: Added 'else if'
147
+ // Note: We don't use WriteIndexExpr here because we need direct array access for swapping
148
+ if err := c.WriteValueExpr(indexExpr.X); err != nil {
149
+ return err
150
+ }
151
+ c.tsw.WriteLiterally("!") // non-null assertion
152
+ c.tsw.WriteLiterally("[")
153
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil {
154
+ return err
155
+ }
156
+ c.tsw.WriteLiterally("]")
157
+ } else {
158
+ // Normal case - write the entire expression
159
+ if err := c.WriteValueExpr(l); err != nil {
160
+ return err
161
+ }
162
+ }
163
+ }
164
+ c.tsw.WriteLiterally("] = [")
165
+ for i, r := range rhs {
166
+ if i != 0 {
167
+ c.tsw.WriteLiterally(", ")
168
+ }
169
+ if indexExpr, ok := r.(*ast.IndexExpr); ok && allIndexExprs {
170
+ // Note: We don't use WriteIndexExpr here because we need direct array access for swapping
171
+ if err := c.WriteValueExpr(indexExpr.X); err != nil {
172
+ return err
173
+ }
174
+ c.tsw.WriteLiterally("!")
175
+ c.tsw.WriteLiterally("[")
176
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil {
177
+ return err
178
+ }
179
+ c.tsw.WriteLiterally("]")
180
+ } else {
181
+ // Normal case - write the entire expression
182
+ if err := c.WriteValueExpr(r); err != nil {
183
+ return err
184
+ }
185
+ }
186
+ }
187
+ c.tsw.WriteLiterally("]")
188
+ return nil
189
+ }
190
+
191
+ // --- Logic for assignments ---
192
+ isMapIndexLHS := false // Track if the first LHS is a map index
193
+ for i, l := range lhs {
194
+ if i != 0 {
195
+ c.tsw.WriteLiterally(", ")
196
+ }
197
+
198
+ // Handle map indexing assignment specially
199
+ // Note: We don't use WriteIndexExpr here because we need to use $.mapSet instead of .get
200
+ currentIsMapIndex := false
201
+ if indexExpr, ok := l.(*ast.IndexExpr); ok {
202
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
203
+ if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
204
+ currentIsMapIndex = true
205
+ if i == 0 {
206
+ isMapIndexLHS = true
207
+ }
208
+ // Use mapSet helper
209
+ c.tsw.WriteLiterally("$.mapSet(")
210
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
211
+ return err
212
+ }
213
+ c.tsw.WriteLiterally(", ")
214
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
215
+ return err
216
+ }
217
+ c.tsw.WriteLiterally(", ")
218
+ // Value will be added after operator and RHS
219
+ }
220
+ }
221
+ }
222
+
223
+ if !currentIsMapIndex {
224
+ // For single assignments, handle boxed variables specially
225
+ if len(lhs) == 1 && len(rhs) == 1 {
226
+ lhsExprIdent, lhsExprIsIdent := l.(*ast.Ident)
227
+ if lhsExprIsIdent {
228
+ // Determine if LHS is boxed
229
+ isLHSBoxed := false
230
+ var lhsObj types.Object
231
+
232
+ // Get the types.Object from the identifier
233
+ if use, ok := c.pkg.TypesInfo.Uses[lhsExprIdent]; ok {
234
+ lhsObj = use
235
+ } else if def, ok := c.pkg.TypesInfo.Defs[lhsExprIdent]; ok {
236
+ lhsObj = def
237
+ }
238
+
239
+ // Check if this variable needs to be boxed
240
+ if lhsObj != nil && c.analysis.NeedsBoxed(lhsObj) {
241
+ isLHSBoxed = true
242
+ }
243
+
244
+ // prevent writing .value unless lhs is boxed
245
+ c.WriteIdent(lhsExprIdent, isLHSBoxed)
246
+ continue
247
+ }
248
+ }
249
+
250
+ // Write the LHS expression normally
251
+ if err := c.WriteValueExpr(l); err != nil {
252
+ return err
253
+ }
254
+ }
255
+ }
256
+
257
+ // Only write the assignment operator for regular variables, not for map assignments handled by mapSet
258
+ if isMapIndexLHS && len(lhs) == 1 { // Only skip operator if it's a single map assignment
259
+ // Continue, we've already written part of the mapSet() function call
260
+ } else {
261
+ c.tsw.WriteLiterally(" ")
262
+ tokStr, ok := TokenToTs(tok) // Use explicit gstypes alias
263
+ if !ok {
264
+ c.tsw.WriteLiterally("?= ")
265
+ c.tsw.WriteCommentLine("Unknown token " + tok.String())
266
+ } else {
267
+ c.tsw.WriteLiterally(tokStr)
268
+ }
269
+ c.tsw.WriteLiterally(" ")
270
+ }
271
+
272
+ // Write RHS
273
+ for i, r := range rhs {
274
+ if i != 0 {
275
+ c.tsw.WriteLiterally(", ")
276
+ }
277
+ // Check if we need to access a boxed source value and apply clone
278
+ // For struct value assignments, we need to handle:
279
+ // 1. Unboxed source, unboxed target: source.clone()
280
+ // 2. Boxed source, unboxed target: source.value.clone()
281
+ // 3. Unboxed source, boxed target: $.box(source)
282
+ // 4. Boxed source, boxed target: source (straight assignment of the box)
283
+
284
+ // Determine if RHS is a boxed variable (could be a struct or other variable)
285
+ needsBoxedAccessRHS := false
286
+ var rhsObj types.Object
287
+
288
+ // Check if RHS is an identifier (variable name)
289
+ rhsIdent, rhsIsIdent := r.(*ast.Ident)
290
+ if rhsIsIdent {
291
+ rhsObj = c.pkg.TypesInfo.Uses[rhsIdent]
292
+ if rhsObj == nil {
293
+ rhsObj = c.pkg.TypesInfo.Defs[rhsIdent]
294
+ }
295
+
296
+ // Important: For struct copying, we need to check if the variable itself is boxed
297
+ // Important: For struct copying, we need to check if the variable needs boxed access
298
+ // This is more comprehensive than just checking if it's boxed
299
+ if rhsObj != nil {
300
+ needsBoxedAccessRHS = c.analysis.NeedsBoxedAccess(rhsObj)
301
+ }
302
+ }
303
+
304
+ // Handle different cases for struct cloning
305
+ if shouldApplyClone(c.pkg, r) {
306
+ // For other expressions, we need to handle boxed access differently
307
+ if _, isIdent := r.(*ast.Ident); isIdent {
308
+ // For identifiers, WriteValueExpr already adds .value if needed
309
+ if err := c.WriteValueExpr(r); err != nil {
310
+ return err
311
+ }
312
+ } else {
313
+ // For non-identifiers, write the expression and add .value if needed
314
+ if err := c.WriteValueExpr(r); err != nil {
315
+ return err
316
+ }
317
+ // Only add .value for non-identifiers that need boxed access
318
+ if needsBoxedAccessRHS {
319
+ c.tsw.WriteLiterally(".value") // Access the boxed value
320
+ }
321
+ }
322
+
323
+ c.tsw.WriteLiterally(".clone()") // Always add clone for struct values
324
+ } else {
325
+ if err := c.WriteValueExpr(r); err != nil { // RHS is a non-struct value
326
+ return err
327
+ }
328
+ }
329
+ }
330
+
331
+ // If the LHS was a single map index, close the mapSet call
332
+ if isMapIndexLHS && len(lhs) == 1 {
333
+ c.tsw.WriteLiterally(")")
334
+ }
335
+ return nil
336
+ }
337
+
338
+ // shouldApplyClone determines whether a `.clone()` method call should be appended
339
+ // to the TypeScript translation of a Go expression `rhs` when it appears on the
340
+ // right-hand side of an assignment. This is primarily to emulate Go's value
341
+ // semantics for struct assignments, where assigning one struct variable to another
342
+ // creates a copy of the struct.
343
+ //
344
+ // It uses `go/types` information (`pkg.TypesInfo`) to determine the type of `rhs`.
345
+ // - If `rhs` is identified as a struct type (either directly, as a named type
346
+ // whose underlying type is a struct, or an unnamed type whose underlying type
347
+ // is a struct), it returns `true`.
348
+ // - An optimization: if `rhs` is a composite literal (`*ast.CompositeLit`),
349
+ // it returns `false` because a composite literal already produces a new value,
350
+ // so cloning is unnecessary.
351
+ // - If type information is unavailable or `rhs` is not a struct type, it returns `false`.
352
+ //
353
+ // This function is crucial for ensuring that assignments of struct values in
354
+ // TypeScript behave like copies, as they do in Go, rather than reference assignments.
355
+ func shouldApplyClone(pkg *packages.Package, rhs ast.Expr) bool {
356
+ if pkg == nil || pkg.TypesInfo == nil {
357
+ // Cannot determine type without type info, default to no clone
358
+ return false
359
+ }
360
+
361
+ // Get the type of the RHS expression
362
+ var exprType types.Type
363
+
364
+ // Handle identifiers (variables) directly - the most common case
365
+ if ident, ok := rhs.(*ast.Ident); ok {
366
+ if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
367
+ // Get the type directly from the object
368
+ exprType = obj.Type()
369
+ } else if obj := pkg.TypesInfo.Defs[ident]; obj != nil {
370
+ // Also check Defs map for definitions
371
+ exprType = obj.Type()
372
+ }
373
+ }
374
+
375
+ // If we couldn't get the type from Uses/Defs, try getting it from Types
376
+ if exprType == nil {
377
+ if tv, found := pkg.TypesInfo.Types[rhs]; found && tv.Type != nil {
378
+ exprType = tv.Type
379
+ }
380
+ }
381
+
382
+ // No type information available
383
+ if exprType == nil {
384
+ return false
385
+ }
386
+
387
+ // Optimization: If it's a composite literal for a struct, no need to clone
388
+ // as it's already a fresh value
389
+ if _, isCompositeLit := rhs.(*ast.CompositeLit); isCompositeLit {
390
+ return false
391
+ }
392
+
393
+ // Check if it's a struct type (directly, through named type, or underlying)
394
+ if named, ok := exprType.(*types.Named); ok {
395
+ if _, isStruct := named.Underlying().(*types.Struct); isStruct {
396
+ return true // Named struct type
397
+ }
398
+ } else if _, ok := exprType.(*types.Struct); ok {
399
+ return true // Direct struct type
400
+ } else if underlying := exprType.Underlying(); underlying != nil {
401
+ if _, isStruct := underlying.(*types.Struct); isStruct {
402
+ return true // Underlying is a struct
403
+ }
404
+ }
405
+
406
+ return false // Not a struct, do not apply clone
407
+ }