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.
@@ -0,0 +1,412 @@
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(), GoTypeContextGeneral)
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 if callExpr, isCallExpr := r.(*ast.CallExpr); isCallExpr {
181
+ // If the RHS is a function call, write it as a call
182
+ if err := c.WriteCallExpr(callExpr); err != nil {
183
+ return err
184
+ }
185
+ } else {
186
+ // Normal case - write the entire expression
187
+ if err := c.WriteValueExpr(r); err != nil {
188
+ return err
189
+ }
190
+ }
191
+ }
192
+ c.tsw.WriteLiterally("]")
193
+ return nil
194
+ }
195
+
196
+ // --- Logic for assignments ---
197
+ isMapIndexLHS := false // Track if the first LHS is a map index
198
+ for i, l := range lhs {
199
+ if i != 0 {
200
+ c.tsw.WriteLiterally(", ")
201
+ }
202
+
203
+ // Handle map indexing assignment specially
204
+ // Note: We don't use WriteIndexExpr here because we need to use $.mapSet instead of .get
205
+ currentIsMapIndex := false
206
+ if indexExpr, ok := l.(*ast.IndexExpr); ok {
207
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
208
+ if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
209
+ currentIsMapIndex = true
210
+ if i == 0 {
211
+ isMapIndexLHS = true
212
+ }
213
+ // Use mapSet helper
214
+ c.tsw.WriteLiterally("$.mapSet(")
215
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
216
+ return err
217
+ }
218
+ c.tsw.WriteLiterally(", ")
219
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
220
+ return err
221
+ }
222
+ c.tsw.WriteLiterally(", ")
223
+ // Value will be added after operator and RHS
224
+ }
225
+ }
226
+ }
227
+
228
+ if !currentIsMapIndex {
229
+ // For single assignments, handle boxed variables specially
230
+ if len(lhs) == 1 && len(rhs) == 1 {
231
+ lhsExprIdent, lhsExprIsIdent := l.(*ast.Ident)
232
+ if lhsExprIsIdent {
233
+ // Determine if LHS is boxed
234
+ isLHSBoxed := false
235
+ var lhsObj types.Object
236
+
237
+ // Get the types.Object from the identifier
238
+ if use, ok := c.pkg.TypesInfo.Uses[lhsExprIdent]; ok {
239
+ lhsObj = use
240
+ } else if def, ok := c.pkg.TypesInfo.Defs[lhsExprIdent]; ok {
241
+ lhsObj = def
242
+ }
243
+
244
+ // Check if this variable needs to be boxed
245
+ if lhsObj != nil && c.analysis.NeedsBoxed(lhsObj) {
246
+ isLHSBoxed = true
247
+ }
248
+
249
+ // prevent writing .value unless lhs is boxed
250
+ c.WriteIdent(lhsExprIdent, isLHSBoxed)
251
+ continue
252
+ }
253
+ }
254
+
255
+ // Write the LHS expression normally
256
+ if err := c.WriteValueExpr(l); err != nil {
257
+ return err
258
+ }
259
+ }
260
+ }
261
+
262
+ // Only write the assignment operator for regular variables, not for map assignments handled by mapSet
263
+ if isMapIndexLHS && len(lhs) == 1 { // Only skip operator if it's a single map assignment
264
+ // Continue, we've already written part of the mapSet() function call
265
+ } else {
266
+ c.tsw.WriteLiterally(" ")
267
+ tokStr, ok := TokenToTs(tok) // Use explicit gstypes alias
268
+ if !ok {
269
+ c.tsw.WriteLiterally("?= ")
270
+ c.tsw.WriteCommentLine("Unknown token " + tok.String())
271
+ } else {
272
+ c.tsw.WriteLiterally(tokStr)
273
+ }
274
+ c.tsw.WriteLiterally(" ")
275
+ }
276
+
277
+ // Write RHS
278
+ for i, r := range rhs {
279
+ if i != 0 {
280
+ c.tsw.WriteLiterally(", ")
281
+ }
282
+ // Check if we need to access a boxed source value and apply clone
283
+ // For struct value assignments, we need to handle:
284
+ // 1. Unboxed source, unboxed target: source.clone()
285
+ // 2. Boxed source, unboxed target: source.value.clone()
286
+ // 3. Unboxed source, boxed target: $.box(source)
287
+ // 4. Boxed source, boxed target: source (straight assignment of the box)
288
+
289
+ // Determine if RHS is a boxed variable (could be a struct or other variable)
290
+ needsBoxedAccessRHS := false
291
+ var rhsObj types.Object
292
+
293
+ // Check if RHS is an identifier (variable name)
294
+ rhsIdent, rhsIsIdent := r.(*ast.Ident)
295
+ if rhsIsIdent {
296
+ rhsObj = c.pkg.TypesInfo.Uses[rhsIdent]
297
+ if rhsObj == nil {
298
+ rhsObj = c.pkg.TypesInfo.Defs[rhsIdent]
299
+ }
300
+
301
+ // Important: For struct copying, we need to check if the variable itself is boxed
302
+ // Important: For struct copying, we need to check if the variable needs boxed access
303
+ // This is more comprehensive than just checking if it's boxed
304
+ if rhsObj != nil {
305
+ needsBoxedAccessRHS = c.analysis.NeedsBoxedAccess(rhsObj)
306
+ }
307
+ }
308
+
309
+ // Handle different cases for struct cloning
310
+ if shouldApplyClone(c.pkg, r) {
311
+ // For other expressions, we need to handle boxed access differently
312
+ if _, isIdent := r.(*ast.Ident); isIdent {
313
+ // For identifiers, WriteValueExpr already adds .value if needed
314
+ if err := c.WriteValueExpr(r); err != nil {
315
+ return err
316
+ }
317
+ } else {
318
+ // For non-identifiers, write the expression and add .value if needed
319
+ if err := c.WriteValueExpr(r); err != nil {
320
+ return err
321
+ }
322
+ // Only add .value for non-identifiers that need boxed access
323
+ if needsBoxedAccessRHS {
324
+ c.tsw.WriteLiterally(".value") // Access the boxed value
325
+ }
326
+ }
327
+
328
+ c.tsw.WriteLiterally(".clone()") // Always add clone for struct values
329
+ } else {
330
+ if err := c.WriteValueExpr(r); err != nil { // RHS is a non-struct value
331
+ return err
332
+ }
333
+ }
334
+ }
335
+
336
+ // If the LHS was a single map index, close the mapSet call
337
+ if isMapIndexLHS && len(lhs) == 1 {
338
+ c.tsw.WriteLiterally(")")
339
+ }
340
+ return nil
341
+ }
342
+
343
+ // shouldApplyClone determines whether a `.clone()` method call should be appended
344
+ // to the TypeScript translation of a Go expression `rhs` when it appears on the
345
+ // right-hand side of an assignment. This is primarily to emulate Go's value
346
+ // semantics for struct assignments, where assigning one struct variable to another
347
+ // creates a copy of the struct.
348
+ //
349
+ // It uses `go/types` information (`pkg.TypesInfo`) to determine the type of `rhs`.
350
+ // - If `rhs` is identified as a struct type (either directly, as a named type
351
+ // whose underlying type is a struct, or an unnamed type whose underlying type
352
+ // is a struct), it returns `true`.
353
+ // - An optimization: if `rhs` is a composite literal (`*ast.CompositeLit`),
354
+ // it returns `false` because a composite literal already produces a new value,
355
+ // so cloning is unnecessary.
356
+ // - If type information is unavailable or `rhs` is not a struct type, it returns `false`.
357
+ //
358
+ // This function is crucial for ensuring that assignments of struct values in
359
+ // TypeScript behave like copies, as they do in Go, rather than reference assignments.
360
+ func shouldApplyClone(pkg *packages.Package, rhs ast.Expr) bool {
361
+ if pkg == nil || pkg.TypesInfo == nil {
362
+ // Cannot determine type without type info, default to no clone
363
+ return false
364
+ }
365
+
366
+ // Get the type of the RHS expression
367
+ var exprType types.Type
368
+
369
+ // Handle identifiers (variables) directly - the most common case
370
+ if ident, ok := rhs.(*ast.Ident); ok {
371
+ if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
372
+ // Get the type directly from the object
373
+ exprType = obj.Type()
374
+ } else if obj := pkg.TypesInfo.Defs[ident]; obj != nil {
375
+ // Also check Defs map for definitions
376
+ exprType = obj.Type()
377
+ }
378
+ }
379
+
380
+ // If we couldn't get the type from Uses/Defs, try getting it from Types
381
+ if exprType == nil {
382
+ if tv, found := pkg.TypesInfo.Types[rhs]; found && tv.Type != nil {
383
+ exprType = tv.Type
384
+ }
385
+ }
386
+
387
+ // No type information available
388
+ if exprType == nil {
389
+ return false
390
+ }
391
+
392
+ // Optimization: If it's a composite literal for a struct, no need to clone
393
+ // as it's already a fresh value
394
+ if _, isCompositeLit := rhs.(*ast.CompositeLit); isCompositeLit {
395
+ return false
396
+ }
397
+
398
+ // Check if it's a struct type (directly, through named type, or underlying)
399
+ if named, ok := exprType.(*types.Named); ok {
400
+ if _, isStruct := named.Underlying().(*types.Struct); isStruct {
401
+ return true // Named struct type
402
+ }
403
+ } else if _, ok := exprType.(*types.Struct); ok {
404
+ return true // Direct struct type
405
+ } else if underlying := exprType.Underlying(); underlying != nil {
406
+ if _, isStruct := underlying.(*types.Struct); isStruct {
407
+ return true // Underlying is a struct
408
+ }
409
+ }
410
+
411
+ return false // Not a struct, do not apply clone
412
+ }