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 +103 -79
- package/compiler/analysis.go +89 -4
- package/compiler/assignment.go +64 -2
- package/compiler/compiler.go +21 -0
- package/compiler/compiler_test.go +11 -1
- package/compiler/composite-lit.go +21 -4
- package/compiler/expr-call.go +41 -3
- package/compiler/expr-selector.go +4 -4
- package/compiler/expr.go +64 -5
- package/compiler/spec-struct.go +7 -3
- package/compiler/spec-value.go +41 -10
- package/compiler/spec.go +31 -11
- package/compiler/stmt-range.go +1 -2
- package/compiler/stmt.go +54 -11
- package/dist/gs/builtin/type.d.ts +1 -0
- package/dist/gs/builtin/type.js +54 -7
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/reflect/iter.js.map +1 -1
- package/gs/builtin/type.ts +58 -8
- package/gs/reflect/iter.ts +4 -6
- package/package.json +1 -1
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
|
|
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
|
|
package/compiler/analysis.go
CHANGED
|
@@ -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
|
|
873
|
-
func (v *analysisVisitor) visitTypeAssertExpr(
|
|
874
|
-
//
|
|
875
|
-
v.
|
|
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
|
|
package/compiler/assignment.go
CHANGED
|
@@ -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
|
package/compiler/compiler.go
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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 {
|
package/compiler/expr-call.go
CHANGED
|
@@ -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
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
386
|
-
|
|
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
|
-
|
|
402
|
-
|
|
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
|
-
|
|
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
|
|