goscript 0.0.55 → 0.0.57

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/README.md CHANGED
@@ -64,6 +64,108 @@ npm install -g goscript
64
64
  goscript compile --package . --output ./dist
65
65
  ```
66
66
 
67
+ ## 📦 Using Generated Code in Your Project
68
+
69
+ After compiling your Go code to TypeScript, you'll need to set up your project appropriately.
70
+
71
+ ### TypeScript Configuration
72
+
73
+ Create or update your `tsconfig.json` with these settings:
74
+
75
+ ```json
76
+ {
77
+ "compilerOptions": {
78
+ "target": "ES2022",
79
+ "module": "ESNext",
80
+ "moduleResolution": "bundler",
81
+ "lib": ["ES2022", "esnext.disposable", "dom"],
82
+ "baseUrl": "./",
83
+ "paths": {
84
+ "@goscript/*": ["./path/to/generated/output/@goscript/*"]
85
+ },
86
+ "allowSyntheticDefaultImports": true,
87
+ "esModuleInterop": true,
88
+ "skipLibCheck": true,
89
+ "strict": true
90
+ }
91
+ }
92
+ ```
93
+
94
+ **Important requirements:**
95
+ - **`target: "ES2022"` or newer** - Required for `Disposable` and other features
96
+ - **`lib: ["esnext.disposable"]`** - Enables TypeScript's disposable types for resource management
97
+ - **`baseUrl` and `paths`** - Allows TypeScript to resolve `@goscript/*` imports
98
+ - **`moduleResolution: "bundler"`** - Recommended for modern bundlers
99
+
100
+ You should be able to use any TypeScript bundler to compile the generated TypeScript.
101
+
102
+ ## 🛠️ Integration & Usage
103
+
104
+ ### Command Line
105
+
106
+ ```bash
107
+ goscript compile --package ./my-go-code --output ./dist
108
+ ```
109
+
110
+ **Options:**
111
+ - `--package <path>` - Go package to compile (default: ".")
112
+ - `--output <dir>` - Output directory for TypeScript files
113
+
114
+ ### Programmatic API
115
+
116
+ **Go:**
117
+ ```go
118
+ import "github.com/aperturerobotics/goscript/compiler"
119
+
120
+ conf := &compiler.Config{OutputPath: "./dist"}
121
+ comp, err := compiler.NewCompiler(conf, logger, nil)
122
+ _, err = comp.CompilePackages(ctx, "your/package/path")
123
+ ```
124
+
125
+ **Node.js:**
126
+ ```typescript
127
+ import { compile } from 'goscript'
128
+
129
+ await compile({
130
+ pkg: './my-go-package',
131
+ output: './dist'
132
+ })
133
+ ```
134
+
135
+ ### Frontend Frameworks
136
+
137
+ **React + GoScript:**
138
+ ```typescript
139
+ import { NewCalculator } from '@goscript/myapp/calculator'
140
+
141
+ function CalculatorApp() {
142
+ const [calc] = useState(() => NewCalculator())
143
+
144
+ const handleAdd = () => {
145
+ const result = calc.Add(5, 3)
146
+ setResult(result)
147
+ }
148
+
149
+ return <button onClick={handleAdd}>Add 5 + 3</button>
150
+ }
151
+ ```
152
+
153
+ **Vue + GoScript:**
154
+ ```vue
155
+ <script setup lang="ts">
156
+ import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
157
+
158
+ const users = ref([
159
+ NewUser(1, "Alice", "alice@example.com")
160
+ ])
161
+
162
+ const searchUser = (email: string) => {
163
+ return FindUserByEmail(users.value, email)
164
+ }
165
+ </script>
166
+ ```
167
+
168
+
67
169
  ## 💡 See It In Action
68
170
 
69
171
  ### Example: User Management
@@ -196,72 +298,6 @@ async function handleMessages() {
196
298
  }
197
299
  ```
198
300
 
199
- ## 🛠️ Integration & Usage
200
-
201
- ### Command Line
202
-
203
- ```bash
204
- goscript compile --package ./my-go-code --output ./dist
205
- ```
206
-
207
- **Options:**
208
- - `--package <path>` - Go package to compile (default: ".")
209
- - `--output <dir>` - Output directory for TypeScript files
210
-
211
- ### Programmatic API
212
-
213
- **Go:**
214
- ```go
215
- import "github.com/aperturerobotics/goscript/compiler"
216
-
217
- conf := &compiler.Config{OutputPath: "./dist"}
218
- comp, err := compiler.NewCompiler(conf, logger, nil)
219
- _, err = comp.CompilePackages(ctx, "your/package/path")
220
- ```
221
-
222
- **Node.js:**
223
- ```typescript
224
- import { compile } from 'goscript'
225
-
226
- await compile({
227
- pkg: './my-go-package',
228
- output: './dist'
229
- })
230
- ```
231
-
232
- ### Frontend Frameworks
233
-
234
- **React + GoScript:**
235
- ```typescript
236
- import { NewCalculator } from '@goscript/myapp/calculator'
237
-
238
- function CalculatorApp() {
239
- const [calc] = useState(() => NewCalculator())
240
-
241
- const handleAdd = () => {
242
- const result = calc.Add(5, 3)
243
- setResult(result)
244
- }
245
-
246
- return <button onClick={handleAdd}>Add 5 + 3</button>
247
- }
248
- ```
249
-
250
- **Vue + GoScript:**
251
- ```vue
252
- <script setup lang="ts">
253
- import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
254
-
255
- const users = ref([
256
- NewUser(1, "Alice", "alice@example.com")
257
- ])
258
-
259
- const searchUser = (email: string) => {
260
- return FindUserByEmail(users.value, email)
261
- }
262
- </script>
263
- ```
264
-
265
301
  ## 🚀 What's Next?
266
302
 
267
303
  **Current Status:**
@@ -276,19 +312,7 @@ const searchUser = (email: string) => {
276
312
  - ⚡ Performance optimizations
277
313
  - 🔧 Better tooling integration
278
314
 
279
- Check our [compliance tests](./compliance/COMPLIANCE.md) for detailed progress.
280
-
281
- ## 🤝 Real-World Use Cases
282
-
283
- **Fintech:** Share complex financial calculations between Go services and trading dashboards
284
-
285
- **Gaming:** Run the same game logic on servers and in browser clients
286
-
287
- **Data Processing:** Use identical algorithms for backend ETL and frontend analytics
288
-
289
- **Validation:** Keep business rules consistent across your entire stack
290
-
291
- Ready to eliminate code duplication? [Get started now](#-get-started-in-2-minutes) 🚀
315
+ Check the [compliance tests](./compliance/COMPLIANCE.md) for detailed progress.
292
316
 
293
317
  ## License
294
318
 
@@ -589,6 +589,12 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
589
589
 
590
590
  case *ast.TypeAssertExpr:
591
591
  return v.visitTypeAssertExpr(n)
592
+
593
+ case *ast.CompositeLit:
594
+ // Traverse into composite literal elements to detect &variable expressions
595
+ // This is important for cases like: arr := []interface{}{value1, &value2}
596
+ // where value2 needs to be marked as NeedsVarRef due to the &value2 usage
597
+ return v.visitCompositeLit(n)
592
598
  }
593
599
 
594
600
  // For all other nodes, continue traversal
@@ -869,10 +875,89 @@ func (v *analysisVisitor) visitIfStmt(n *ast.IfStmt) ast.Visitor {
869
875
  return v
870
876
  }
871
877
 
872
- // visitTypeAssertExpr handles type assertion expression analysis
873
- func (v *analysisVisitor) visitTypeAssertExpr(n *ast.TypeAssertExpr) ast.Visitor {
874
- // Track interface implementations when we see type assertions
875
- v.trackTypeAssertion(n)
878
+ // visitTypeAssertExpr handles type assertion analysis for interface method implementations
879
+ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) ast.Visitor {
880
+ // Get the type being asserted to
881
+ assertedType := v.pkg.TypesInfo.TypeOf(typeAssert.Type)
882
+ if assertedType == nil {
883
+ return v
884
+ }
885
+
886
+ // Check if the asserted type is an interface
887
+ interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
888
+ if !isInterface {
889
+ return v
890
+ }
891
+
892
+ // Get the type of the expression being asserted
893
+ exprType := v.pkg.TypesInfo.TypeOf(typeAssert.X)
894
+ if exprType == nil {
895
+ return v
896
+ }
897
+
898
+ // Handle pointer types by getting the element type
899
+ if ptrType, isPtr := exprType.(*types.Pointer); isPtr {
900
+ exprType = ptrType.Elem()
901
+ }
902
+
903
+ // Check if the expression type is a named struct type
904
+ namedType, isNamed := exprType.(*types.Named)
905
+ if !isNamed {
906
+ return v
907
+ }
908
+
909
+ // For each method in the interface, check if the struct implements it
910
+ for i := 0; i < interfaceType.NumExplicitMethods(); i++ {
911
+ interfaceMethod := interfaceType.ExplicitMethod(i)
912
+
913
+ // Find the corresponding method in the struct type
914
+ structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
915
+ if structMethod != nil {
916
+ // Determine if this struct method is async using unified system
917
+ isAsync := false
918
+ if obj := structMethod; obj != nil {
919
+ isAsync = v.analysis.IsAsyncFunc(obj)
920
+ }
921
+
922
+ // Track this interface implementation
923
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
924
+ }
925
+ }
926
+ return v
927
+ }
928
+
929
+ // visitCompositeLit analyzes composite literals for address-of expressions
930
+ // This is important for detecting cases like: arr := []interface{}{value1, &value2}
931
+ // where value2 needs to be marked as NeedsVarRef due to the &value2 usage
932
+ func (v *analysisVisitor) visitCompositeLit(compLit *ast.CompositeLit) ast.Visitor {
933
+ // Analyze each element of the composite literal
934
+ for _, elt := range compLit.Elts {
935
+ // Handle both direct elements and key-value pairs
936
+ var expr ast.Expr
937
+ if kv, ok := elt.(*ast.KeyValueExpr); ok {
938
+ // For key-value pairs, analyze the value expression
939
+ expr = kv.Value
940
+ } else {
941
+ // For direct elements, analyze the element expression
942
+ expr = elt
943
+ }
944
+
945
+ // Check if this element is an address-of expression
946
+ if unaryExpr, ok := expr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
947
+ // Found &something in the composite literal
948
+ if ident, ok := unaryExpr.X.(*ast.Ident); ok {
949
+ // Found &variable - mark the variable as needing VarRef
950
+ if obj := v.pkg.TypesInfo.ObjectOf(ident); obj != nil {
951
+ // Record that this variable has its address taken
952
+ usageInfo := v.getOrCreateUsageInfo(obj)
953
+ usageInfo.Destinations = append(usageInfo.Destinations, AssignmentInfo{
954
+ Object: nil, // No specific destination object for composite literals
955
+ Type: AddressOfAssignment,
956
+ })
957
+ }
958
+ }
959
+ }
960
+ }
876
961
  return v
877
962
  }
878
963
 
@@ -56,10 +56,12 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
56
56
 
57
57
  // Handle the RHS expression (potentially adding .clone() for structs)
58
58
  if shouldApplyClone(c.pkg, rhs[0]) {
59
+ // When cloning for value assignment, mark the result as struct value
60
+ c.tsw.WriteLiterally("$.markAsStructValue(")
59
61
  if err := c.WriteValueExpr(rhs[0]); err != nil {
60
62
  return err
61
63
  }
62
- c.tsw.WriteLiterally(".clone()")
64
+ c.tsw.WriteLiterally(".clone())")
63
65
  } else {
64
66
  if err := c.WriteValueExpr(rhs[0]); err != nil {
65
67
  return err
@@ -338,8 +340,37 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
338
340
  }
339
341
  }
340
342
 
343
+ // Check for pointer-to-pointer assignment
344
+ if rhsIsIdent && rhsObj != nil && len(lhs) == 1 {
345
+ lhsType := c.pkg.TypesInfo.TypeOf(lhs[0])
346
+ rhsType := rhsObj.Type()
347
+
348
+ if lhsType != nil && rhsType != nil {
349
+ // Check if both LHS and RHS are pointer types
350
+ if _, lhsIsPtr := lhsType.(*types.Pointer); lhsIsPtr {
351
+ if _, rhsIsPtr := rhsType.(*types.Pointer); rhsIsPtr {
352
+ // This is pointer-to-pointer assignment
353
+ // The key question: is the RHS variable itself varref'd?
354
+ // - If RHS is varref'd (like pp1), use .value to get the actual pointer
355
+ // - If RHS is not varref'd (like p1), use the variable directly
356
+
357
+ if c.analysis.NeedsVarRef(rhsObj) {
358
+ // RHS variable is varref'd, so we need its .value to get the actual pointer
359
+ c.WriteIdent(rhsIdent, true) // Add .value access
360
+ } else {
361
+ // RHS variable is not varref'd, so it directly holds the pointer
362
+ c.WriteIdent(rhsIdent, false) // No .value access
363
+ }
364
+ continue
365
+ }
366
+ }
367
+ }
368
+ }
369
+
341
370
  // Handle different cases for struct cloning
342
371
  if shouldApplyClone(c.pkg, r) {
372
+ // When cloning for value assignment, mark the result as struct value
373
+ c.tsw.WriteLiterally("$.markAsStructValue(")
343
374
  // For other expressions, we need to handle variable referenced access differently
344
375
  if _, isIdent := r.(*ast.Ident); isIdent {
345
376
  // For identifiers, WriteValueExpr already adds .value if needed
@@ -357,8 +388,39 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
357
388
  }
358
389
  }
359
390
 
360
- c.tsw.WriteLiterally(".clone()") // Always add clone for struct values
391
+ c.tsw.WriteLiterally(".clone())") // Always add clone for struct values
361
392
  } else {
393
+ // Check if this is a pointer variable assignment to an interface type
394
+ if rhsIsIdent && rhsObj != nil {
395
+ // Check if LHS is interface type and RHS is a pointer variable
396
+ if len(lhs) == 1 {
397
+ lhsType := c.pkg.TypesInfo.TypeOf(lhs[0])
398
+ rhsType := rhsObj.Type()
399
+
400
+ if lhsType != nil && rhsType != nil {
401
+ // Check if LHS is interface and RHS is pointer
402
+ if _, isInterface := lhsType.Underlying().(*types.Interface); isInterface {
403
+ if ptrType, isPtr := rhsType.(*types.Pointer); isPtr {
404
+ // This is pointer-to-interface assignment
405
+ // For pointer variables that point to varrefed values, write without .value
406
+ // We want to pass the VarRef object itself to the interface, not its .value
407
+ if c.analysis.NeedsVarRefAccess(rhsObj) {
408
+ // Write the pointer variable without .value access
409
+ c.WriteIdent(rhsIdent, false)
410
+ continue
411
+ }
412
+
413
+ // Check if this is a struct pointer for the element type
414
+ if _, isStruct := ptrType.Elem().Underlying().(*types.Struct); isStruct {
415
+ // Struct pointer to interface - might need special handling
416
+ // Continue to normal WriteValueExpr handling
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+
362
424
  // Non-struct case: write RHS normally
363
425
  if err := c.WriteValueExpr(r); err != nil { // RHS is a non-struct value
364
426
  return err
@@ -434,6 +434,24 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
434
434
  case *ast.FuncDecl:
435
435
  if d.Recv == nil && d.Name.IsExported() {
436
436
  valueSymbols = append(valueSymbols, sanitizeIdentifier(d.Name.Name))
437
+ } else if d.Recv != nil && len(d.Recv.List) == 1 && d.Name.IsExported() {
438
+ recvField := d.Recv.List[0]
439
+ recvTypeExpr := recvField.Type
440
+ if star, ok := recvTypeExpr.(*ast.StarExpr); ok {
441
+ recvTypeExpr = star.X
442
+ }
443
+ if ident, ok := recvTypeExpr.(*ast.Ident); ok {
444
+ typeObj := c.pkg.TypesInfo.ObjectOf(ident)
445
+ if typeObj != nil && typeObj.Exported() {
446
+ if typeName, ok := typeObj.(*types.TypeName); ok {
447
+ underlying := typeName.Type().Underlying()
448
+ if _, isStruct := underlying.(*types.Struct); !isStruct {
449
+ methodName := sanitizeIdentifier(ident.Name + "_" + d.Name.Name)
450
+ valueSymbols = append(valueSymbols, methodName)
451
+ }
452
+ }
453
+ }
454
+ }
437
455
  }
438
456
  case *ast.GenDecl:
439
457
  for _, spec := range d.Specs {
@@ -686,6 +704,9 @@ type GoToTSCompiler struct {
686
704
  pkg *packages.Package
687
705
 
688
706
  analysis *Analysis
707
+
708
+ // Context flags
709
+ insideAddressOf bool // true when processing operand of & operator
689
710
  }
690
711
 
691
712
  // It initializes the compiler with a `TSCodeWriter` for output,
@@ -1,6 +1,7 @@
1
1
  package compiler_test
2
2
 
3
3
  import (
4
+ "fmt"
4
5
  "os"
5
6
  "os/exec"
6
7
  "path/filepath"
@@ -135,5 +136,14 @@ func getParentGoModulePath() (string, error) {
135
136
  if err != nil {
136
137
  return "", err
137
138
  }
138
- return strings.TrimSpace(string(output)), nil
139
+ // note: in a go work configuration, go list -m can report multiple modules
140
+ // only one of which is the goscript case, so we need to filter:
141
+ pf := strings.Fields(strings.TrimSpace(string(output)))
142
+ pf = slices.DeleteFunc(pf, func(n string) bool {
143
+ return !strings.HasSuffix(n, "goscript")
144
+ })
145
+ if len(pf) != 1 {
146
+ return "", fmt.Errorf("'go list -m' did not have exactly 1 goscript package -- run in root of goscript package")
147
+ }
148
+ return pf[0], nil
139
149
  }
@@ -212,6 +212,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
212
212
  var structType *types.Struct
213
213
  isStructLiteral := false
214
214
  isAnonymousStruct := false
215
+ needsValueMarkerClose := false // Track if we need to close $.markAsStructValue()
215
216
 
216
217
  if namedType, ok := litType.(*types.Named); ok {
217
218
  if underlyingStruct, ok := namedType.Underlying().(*types.Struct); ok {
@@ -224,8 +225,14 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
224
225
  return err
225
226
  }
226
227
  } else {
227
- // Named struct, use constructor
228
- c.tsw.WriteLiterally("new ")
228
+ // Named struct value, use constructor
229
+ if !c.insideAddressOf {
230
+ // Only mark as struct value if not inside address-of operator
231
+ c.tsw.WriteLiterally("$.markAsStructValue(new ")
232
+ needsValueMarkerClose = true
233
+ } else {
234
+ c.tsw.WriteLiterally("new ")
235
+ }
229
236
  c.WriteTypeExpr(exp.Type)
230
237
  }
231
238
  }
@@ -241,8 +248,14 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
241
248
  return err
242
249
  }
243
250
  } else {
244
- // Type alias for struct, use constructor
245
- c.tsw.WriteLiterally("new ")
251
+ // Type alias for struct value, use constructor
252
+ if !c.insideAddressOf {
253
+ // Only mark as struct value if not inside address-of operator
254
+ c.tsw.WriteLiterally("$.markAsStructValue(new ")
255
+ needsValueMarkerClose = true
256
+ } else {
257
+ c.tsw.WriteLiterally("new ")
258
+ }
246
259
  c.WriteTypeExpr(exp.Type)
247
260
  }
248
261
  }
@@ -483,6 +496,10 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
483
496
  c.tsw.WriteLiterally("}")
484
497
  } else {
485
498
  c.tsw.WriteLiterally("})")
499
+ // Close markAsStructValue wrapper if we opened one
500
+ if needsValueMarkerClose {
501
+ c.tsw.WriteLiterally(")")
502
+ }
486
503
  }
487
504
 
488
505
  } else {
@@ -349,9 +349,47 @@ func (c *GoToTSCompiler) writeWrapperTypeMethodCall(exp *ast.CallExpr, selectorE
349
349
  c.tsw.WriteLiterally(selectorExpr.Sel.Name)
350
350
  c.tsw.WriteLiterally("(")
351
351
 
352
- // First argument is the receiver
353
- if err := c.WriteValueExpr(selectorExpr.X); err != nil {
354
- return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
352
+ // Write the receiver (the object the method is called on)
353
+ // For pointer receiver methods, we need to pass the VarRef instead of the value
354
+ receiverNeedsVarRef := false
355
+
356
+ // Check if the method has a pointer receiver by looking at the method signature
357
+ if selection := c.pkg.TypesInfo.Selections[selectorExpr]; selection != nil {
358
+ if methodObj := selection.Obj(); methodObj != nil {
359
+ if methodFunc, ok := methodObj.(*types.Func); ok {
360
+ if sig, ok := methodFunc.Type().(*types.Signature); ok && sig != nil {
361
+ if recv := sig.Recv(); recv != nil {
362
+ if _, isPointer := recv.Type().(*types.Pointer); isPointer {
363
+ receiverNeedsVarRef = true
364
+ }
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }
370
+
371
+ if receiverNeedsVarRef {
372
+ // For pointer receivers, we need to pass the VarRef
373
+ // Convert p.field to p._fields.field
374
+ if selExpr, ok := selectorExpr.X.(*ast.SelectorExpr); ok {
375
+ if baseIdent, ok := selExpr.X.(*ast.Ident); ok {
376
+ c.tsw.WriteLiterally(baseIdent.Name)
377
+ c.tsw.WriteLiterally("._fields.")
378
+ c.tsw.WriteLiterally(selExpr.Sel.Name)
379
+ } else {
380
+ if err := c.WriteValueExpr(selectorExpr.X); err != nil {
381
+ return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
382
+ }
383
+ }
384
+ } else {
385
+ if err := c.WriteValueExpr(selectorExpr.X); err != nil {
386
+ return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
387
+ }
388
+ }
389
+ } else {
390
+ if err := c.WriteValueExpr(selectorExpr.X); err != nil {
391
+ return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
392
+ }
355
393
  }
356
394
 
357
395
  // Add other arguments
@@ -239,24 +239,24 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
239
239
  // The receiver should be a copy of the dereferenced value
240
240
  c.tsw.WriteLiterally(".value.")
241
241
  c.WriteIdent(exp.Sel, false)
242
- c.tsw.WriteLiterally(".bind(")
242
+ c.tsw.WriteLiterally(".bind($.markAsStructValue(")
243
243
  if err := c.WriteValueExpr(exp.X); err != nil {
244
244
  return fmt.Errorf("failed to write method value receiver for binding: %w", err)
245
245
  }
246
- c.tsw.WriteLiterally("!.value.clone())")
246
+ c.tsw.WriteLiterally("!.value.clone()))")
247
247
  } else if !isPointerReceiver && !baseIsPointer {
248
248
  // Value receiver method on value type: t.Mv
249
249
  // The receiver should be a copy of the value
250
250
  c.tsw.WriteLiterally(".")
251
251
  c.WriteIdent(exp.Sel, false)
252
- c.tsw.WriteLiterally(".bind(")
252
+ c.tsw.WriteLiterally(".bind($.markAsStructValue(")
253
253
  if err := c.WriteValueExpr(exp.X); err != nil {
254
254
  return fmt.Errorf("failed to write method value receiver for binding: %w", err)
255
255
  }
256
256
  if baseIsPointer {
257
257
  c.tsw.WriteLiterally("!")
258
258
  }
259
- c.tsw.WriteLiterally(".clone())")
259
+ c.tsw.WriteLiterally(".clone()))")
260
260
  } else {
261
261
  // Pointer receiver method on pointer type: pt.Mp
262
262
  // The receiver should be the pointer itself
package/compiler/expr.go CHANGED
@@ -382,9 +382,45 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
382
382
  // Compare the varRef objects directly using === or !==
383
383
  if c.isPointerComparison(exp) {
384
384
  c.tsw.WriteLiterally("(") // Wrap comparison
385
- if err := c.WriteValueExpr(exp.X); err != nil {
386
- return fmt.Errorf("failed to write binary expression left operand: %w", err)
385
+
386
+ // For pointer comparisons, we need to handle variable varref status
387
+ // If a variable is varref'd, we need its .value to get the actual pointer value
388
+
389
+ // Check if operands are varref'd variables
390
+ var leftObj, rightObj types.Object
391
+ leftIsVarRef := false
392
+ rightIsVarRef := false
393
+
394
+ if leftIdent, ok := exp.X.(*ast.Ident); ok {
395
+ leftObj = c.pkg.TypesInfo.ObjectOf(leftIdent)
396
+ if leftObj != nil {
397
+ leftIsVarRef = c.analysis.NeedsVarRef(leftObj)
398
+ }
399
+ }
400
+
401
+ if rightIdent, ok := exp.Y.(*ast.Ident); ok {
402
+ rightObj = c.pkg.TypesInfo.ObjectOf(rightIdent)
403
+ if rightObj != nil {
404
+ rightIsVarRef = c.analysis.NeedsVarRef(rightObj)
405
+ }
387
406
  }
407
+
408
+ // Write left operand
409
+ if leftIdent, ok := exp.X.(*ast.Ident); ok {
410
+ if leftIsVarRef {
411
+ // Variable is varref'd, access its .value to get the pointer
412
+ c.WriteIdent(leftIdent, true)
413
+ } else {
414
+ // Variable is not varref'd, use it directly
415
+ c.WriteIdent(leftIdent, false)
416
+ }
417
+ } else {
418
+ // For non-identifiers, use WriteValueExpr
419
+ if err := c.WriteValueExpr(exp.X); err != nil {
420
+ return fmt.Errorf("failed to write binary expression left operand: %w", err)
421
+ }
422
+ }
423
+
388
424
  c.tsw.WriteLiterally(" ")
389
425
  // Use === for == and !== for !=
390
426
  tokStr := ""
@@ -398,9 +434,23 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
398
434
  }
399
435
  c.tsw.WriteLiterally(tokStr)
400
436
  c.tsw.WriteLiterally(" ")
401
- if err := c.WriteValueExpr(exp.Y); err != nil {
402
- return fmt.Errorf("failed to write binary expression right operand: %w", err)
437
+
438
+ // Write right operand
439
+ if rightIdent, ok := exp.Y.(*ast.Ident); ok {
440
+ if rightIsVarRef {
441
+ // Variable is varref'd, access its .value to get the pointer
442
+ c.WriteIdent(rightIdent, true)
443
+ } else {
444
+ // Variable is not varref'd, use it directly
445
+ c.WriteIdent(rightIdent, false)
446
+ }
447
+ } else {
448
+ // For non-identifiers, use WriteValueExpr
449
+ if err := c.WriteValueExpr(exp.Y); err != nil {
450
+ return fmt.Errorf("failed to write binary expression right operand: %w", err)
451
+ }
403
452
  }
453
+
404
454
  c.tsw.WriteLiterally(")") // Close wrap
405
455
  return nil
406
456
  }
@@ -503,12 +553,21 @@ func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
503
553
  }
504
554
  }
505
555
 
556
+ // Note: With inversion to markAsStructValue, we no longer mark &CompositeLit{}
557
+ // since we now mark the CompositeLit{} (struct values) instead of pointers
558
+
506
559
  // Otherwise (&unvarrefedVar, &CompositeLit{}, &FuncCall(), etc.),
507
560
  // the address-of operator in Go, when used to create a pointer,
508
561
  // translates to simply evaluating the operand in TypeScript.
509
562
  // The resulting value (e.g., a new object instance) acts as the "pointer".
510
563
  // VarRefing decisions are handled at the assignment site based on the LHS variable.
511
- if err := c.WriteValueExpr(exp.X); err != nil {
564
+
565
+ // Set context flag to prevent marking composite literals as struct values
566
+ c.insideAddressOf = true
567
+ err := c.WriteValueExpr(exp.X)
568
+ c.insideAddressOf = false
569
+
570
+ if err != nil {
512
571
  return fmt.Errorf("failed to write &-operand: %w", err)
513
572
  }
514
573