goscript 0.0.58 → 0.0.60

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.
Files changed (44) hide show
  1. package/README.md +40 -33
  2. package/compiler/analysis.go +184 -43
  3. package/compiler/assignment.go +163 -217
  4. package/compiler/compiler.go +35 -31
  5. package/compiler/composite-lit.go +233 -196
  6. package/compiler/constraint.go +88 -0
  7. package/compiler/decl.go +82 -24
  8. package/compiler/expr-call-async.go +20 -34
  9. package/compiler/expr-call-builtins.go +19 -0
  10. package/compiler/expr-call-helpers.go +0 -28
  11. package/compiler/expr-call-make.go +93 -343
  12. package/compiler/expr-call-type-conversion.go +221 -249
  13. package/compiler/expr-call.go +70 -69
  14. package/compiler/expr-selector.go +21 -24
  15. package/compiler/expr.go +3 -60
  16. package/compiler/protobuf.go +180 -36
  17. package/compiler/spec-value.go +132 -24
  18. package/compiler/spec.go +14 -55
  19. package/compiler/stmt-assign.go +338 -356
  20. package/compiler/stmt-range.go +4 -24
  21. package/compiler/stmt.go +92 -203
  22. package/compiler/type-utils.go +185 -0
  23. package/compiler/type.go +26 -80
  24. package/dist/gs/builtin/slice.d.ts +1 -1
  25. package/dist/gs/builtin/slice.js +3 -0
  26. package/dist/gs/builtin/slice.js.map +1 -1
  27. package/dist/gs/builtin/type.js +8 -2
  28. package/dist/gs/builtin/type.js.map +1 -1
  29. package/dist/gs/fmt/fmt.js +113 -16
  30. package/dist/gs/fmt/fmt.js.map +1 -1
  31. package/dist/gs/runtime/runtime.d.ts +1 -1
  32. package/dist/gs/runtime/runtime.js +1 -1
  33. package/dist/gs/slices/slices.d.ts +23 -0
  34. package/dist/gs/slices/slices.js +61 -0
  35. package/dist/gs/slices/slices.js.map +1 -1
  36. package/go.mod +10 -10
  37. package/go.sum +22 -14
  38. package/gs/builtin/slice.ts +5 -2
  39. package/gs/builtin/type.ts +13 -6
  40. package/gs/fmt/fmt.test.ts +176 -0
  41. package/gs/fmt/fmt.ts +109 -18
  42. package/gs/runtime/runtime.ts +1 -1
  43. package/gs/slices/slices.ts +68 -0
  44. package/package.json +3 -3
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## What is GoScript?
11
11
 
12
- GoScript is a **Go to TypeScript compiler** that translates Go code to TypeScript at the AST level. Perfect for sharing algorithms and business logic between Go backends and TypeScript frontends.
12
+ GoScript is an experimental **Go to TypeScript compiler** that translates Go code to TypeScript at the AST level. The goal is to enable sharing algorithms and business logic between Go backends and TypeScript frontends.
13
13
 
14
14
  > Right now goscript looks pretty cool if you problem is "I want this self-sufficient algorithm be available in Go and JS runtimes". gopherjs's ambition, however, has always been "any valid Go program can run in a browser". There is a lot that goes on in gopherjs that is necessary for supporting the standard library, which goes beyond cross-language translation.
15
15
  >
@@ -17,33 +17,46 @@ GoScript is a **Go to TypeScript compiler** that translates Go code to TypeScrip
17
17
 
18
18
  ### 🎯 Why GoScript?
19
19
 
20
- **Write once, run everywhere.** Share your Go algorithms, business logic, and data structures seamlessly between your backend and frontend without maintaining two codebases.
20
+ Write once, run everywhere. Share your Go algorithms, business logic, and data structures seamlessly between your backend and frontend without maintaining two codebases.
21
21
 
22
- **Perfect for:**
22
+ 🔬 **Experimental features being developed:**
23
23
  - Sharing business logic between Go services and web apps
24
24
  - Porting Go algorithms to run in browsers
25
25
  - Building TypeScript libraries from existing Go code
26
- - Full-stack teams that love Go's simplicity
27
26
 
28
- ⚠️ **What's supported:**
29
- GoScript compiles a powerful subset of Go:
30
- - Structs, interfaces, methods, and functions
31
- - Channels and goroutines (translated to async/await)
32
- - Slices, maps, and most built-in types
33
- - Basic reflection support
34
- - Standard control flow (if, for, switch, etc.)
27
+ Go has powerful concurrency support and an excellent standard library. GoScript brings these capabilities to TypeScript with as simple and readable of a translation as possible.
35
28
 
36
- **Current limitations:**
29
+ ⚠️ **Current development status:**
30
+ GoScript is working on compiling a subset of Go:
31
+ - ✅ Basic structs, interfaces, methods, and functions
32
+ - ✅ Channels and goroutines (translating to async/await)
33
+ - ✅ Slice semantics, maps, and built-in types
34
+ - ✅ Standard control flow (if, for, switch, select, range, etc.)
35
+ - 🚧 Basic reflection support
36
+ - 🚧 Standard library support
37
+
38
+ **Known limitations in this preview:**
37
39
  - Uses JavaScript `number` type (64-bit float, not Go's int types)
38
40
  - No pointer arithmetic (`uintptr`) or `unsafe` package
39
41
  - No complex numbers
40
- - Limited standard library (growing rapidly)
42
+ - Limited standard library (working on it)
43
+ - Performance not yet optimized
44
+
45
+ **This is a prototype!** Expect bugs and missing features. Please contribute!
46
+
47
+ ### ⚠️ Development Preview
48
+
49
+ **This project is currently in active development and should be considered experimental.** GoScript is a work-in-progress prototype that may have bugs, incomplete features, and breaking changes. We're actively iterating on the compiler and welcome your feedback!
50
+
51
+ 🐛 **Found an issue?** Please [open an issue](https://github.com/aperturerobotics/goscript/issues) and we'll fix it.
41
52
 
42
- If you're building algorithms, business logic, or data processing code, GoScript has you covered! 🚀
53
+ 🤖 **AI** This prototype is heavily AI-written, but I plan to manually rewrite the codebase by hand once it reaches a working state for advanced use cases. AI Producer, human Reducer.
43
54
 
44
55
  📖 **Learn more:** [Design document](./design/DESIGN.md) | [Compliance tests](./compliance/COMPLIANCE.md)
45
56
 
46
- ## 🚀 Get Started in 2 Minutes
57
+ ## 🚀 Try It
58
+
59
+ > **Warning:** This is experimental software. Features may be incomplete or broken. Please report any issues!
47
60
 
48
61
  ### Installation
49
62
 
@@ -52,18 +65,20 @@ If you're building algorithms, business logic, or data processing code, GoScript
52
65
  go install github.com/aperturerobotics/goscript/cmd/goscript@latest
53
66
  ```
54
67
 
55
- **Option 2: NPM**
68
+ **Option 2: NPM** (if available)
56
69
  ```bash
57
70
  npm install -g goscript
58
71
  ```
59
72
 
60
- ### Your First Compilation
73
+ ### Compilation
61
74
 
62
75
  ```bash
63
- # Compile your Go package to TypeScript
76
+ # Try compiling your Go package to TypeScript
64
77
  goscript compile --package . --output ./dist
65
78
  ```
66
79
 
80
+ **Note:** Many Go packages may not compile successfully yet. Start with simple code and gradually test more complex features.
81
+
67
82
  ## 📦 Using Generated Code in Your Project
68
83
 
69
84
  After compiling your Go code to TypeScript, you'll need to set up your project appropriately.
@@ -168,6 +183,8 @@ const searchUser = (email: string) => {
168
183
 
169
184
  ## 💡 See It In Action
170
185
 
186
+ > **Disclaimer:** These examples represent the target functionality. Your mileage may vary with the current development preview.
187
+
171
188
  ### Example: User Management
172
189
 
173
190
  **Go Code** (`user.go`):
@@ -203,7 +220,7 @@ func FindUserByEmail(users []*User, email string) *User {
203
220
  goscript compile --package . --output ./dist
204
221
  ```
205
222
 
206
- **Generated TypeScript** (`user.ts`):
223
+ **Generated TypeScript** (`user.gs.ts`):
207
224
  ```typescript
208
225
  export class User {
209
226
  public ID: number = 0
@@ -298,21 +315,11 @@ async function handleMessages() {
298
315
  }
299
316
  ```
300
317
 
301
- ## 🚀 What's Next?
302
-
303
- **Current Status:**
304
- - ✅ Core language features (structs, methods, interfaces)
305
- - ✅ Async/await for goroutines and channels
306
- - ✅ Basic reflection support
307
- - ✅ Most control flow and data types
308
-
309
- **Coming Soon:**
310
- - 📦 Expanded standard library
311
- - 🧪 Go test → TypeScript test conversion
312
- - ⚡ Performance optimizations
313
- - 🔧 Better tooling integration
318
+ ## 🤝 How You Can Help
314
319
 
315
- Check the [compliance tests](./compliance/COMPLIANCE.md) for detailed progress.
320
+ - Try GoScript on your code and [report issues](https://github.com/aperturerobotics/goscript/issues)
321
+ - Check the [compliance tests](./compliance/COMPLIANCE.md) for current progress
322
+ - Contribute test cases for edge cases you discover
316
323
 
317
324
  ## License
318
325
 
@@ -2,7 +2,6 @@ package compiler
2
2
 
3
3
  import (
4
4
  "encoding/json"
5
- "fmt"
6
5
  "go/ast"
7
6
  "go/token"
8
7
  "go/types"
@@ -641,6 +640,22 @@ func (v *analysisVisitor) visitFuncDecl(n *ast.FuncDecl) ast.Visitor {
641
640
  funcInfo := v.analysis.ensureFunctionData(obj)
642
641
  funcInfo.ReceiverUsed = receiverUsed
643
642
  }
643
+
644
+ // If receiver is used, mark all identifiers that refer to the receiver variable
645
+ if receiverUsed && n.Body != nil {
646
+ recvName := ident.Name
647
+ ast.Inspect(n.Body, func(nn ast.Node) bool {
648
+ id, ok := nn.(*ast.Ident)
649
+ if !ok {
650
+ return true
651
+ }
652
+ if obj := v.pkg.TypesInfo.Uses[id]; obj != nil && obj == vr {
653
+ ni := v.analysis.ensureNodeData(id)
654
+ ni.IdentifierMapping = recvName
655
+ }
656
+ return true
657
+ })
658
+ }
644
659
  }
645
660
  }
646
661
  }
@@ -759,9 +774,92 @@ func (v *analysisVisitor) visitCallExpr(n *ast.CallExpr) ast.Visitor {
759
774
  // Track interface implementations from function call arguments
760
775
  v.trackInterfaceCallArguments(n)
761
776
 
777
+ // Check for implicit address-taking in method calls with pointer receivers
778
+ v.checkImplicitAddressTaking(n)
779
+
762
780
  return v
763
781
  }
764
782
 
783
+ // checkImplicitAddressTaking detects when a method call with a pointer receiver
784
+ // is called on a non-pointer variable, which requires implicit address-taking.
785
+ // Example: var s MySlice; s.Add(10) where Add has receiver *MySlice
786
+ // This is equivalent to (&s).Add(10), so s needs to be marked as NeedsVarRef
787
+ func (v *analysisVisitor) checkImplicitAddressTaking(callExpr *ast.CallExpr) {
788
+ // Check if this is a method call (selector expression)
789
+ selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
790
+ if !ok {
791
+ return
792
+ }
793
+
794
+ // Get the selection information
795
+ selection := v.pkg.TypesInfo.Selections[selExpr]
796
+ if selection == nil || selection.Kind() != types.MethodVal {
797
+ return
798
+ }
799
+
800
+ // Get the method object
801
+ methodObj := selection.Obj()
802
+ if methodObj == nil {
803
+ return
804
+ }
805
+
806
+ // Get the method's signature to check the receiver type
807
+ methodFunc, ok := methodObj.(*types.Func)
808
+ if !ok {
809
+ return
810
+ }
811
+
812
+ sig := methodFunc.Type().(*types.Signature)
813
+ recv := sig.Recv()
814
+ if recv == nil {
815
+ return
816
+ }
817
+
818
+ // Check if the method has a pointer receiver
819
+ recvType := recv.Type()
820
+ _, hasPointerReceiver := recvType.(*types.Pointer)
821
+ if !hasPointerReceiver {
822
+ return
823
+ }
824
+
825
+ // Get the type of the receiver expression (the thing before the dot)
826
+ exprType := v.pkg.TypesInfo.TypeOf(selExpr.X)
827
+ if exprType == nil {
828
+ return
829
+ }
830
+
831
+ // Check if the receiver expression is NOT already a pointer
832
+ _, exprIsPointer := exprType.(*types.Pointer)
833
+ if exprIsPointer {
834
+ // Expression is already a pointer, no implicit address-taking needed
835
+ return
836
+ }
837
+
838
+ // At this point, we have:
839
+ // - A method with a pointer receiver
840
+ // - Being called on a non-pointer expression
841
+ // This means Go will implicitly take the address
842
+
843
+ // Check if the receiver expression is an identifier (variable)
844
+ ident, ok := selExpr.X.(*ast.Ident)
845
+ if !ok {
846
+ return
847
+ }
848
+
849
+ // Get the variable object
850
+ obj := v.pkg.TypesInfo.ObjectOf(ident)
851
+ if obj == nil {
852
+ return
853
+ }
854
+
855
+ // Mark this variable as needing VarRef (its address is being taken)
856
+ usageInfo := v.getOrCreateUsageInfo(obj)
857
+ usageInfo.Destinations = append(usageInfo.Destinations, AssignmentInfo{
858
+ Object: nil, // No specific destination for method calls
859
+ Type: AddressOfAssignment,
860
+ })
861
+ }
862
+
765
863
  // visitSelectorExpr handles selector expression analysis
766
864
  func (v *analysisVisitor) visitSelectorExpr(n *ast.SelectorExpr) ast.Visitor {
767
865
  // Check if this is a method value (method being used as a value, not called immediately)
@@ -1373,6 +1471,14 @@ func (a *Analysis) loadGsMetadata(metaFilePath string) *GsMetadata {
1373
1471
  return &metadata
1374
1472
  }
1375
1473
 
1474
+ // isHandwrittenPackage checks if a package path corresponds to a handwritten package in gs/
1475
+ func (a *Analysis) isHandwrittenPackage(pkgPath string) bool {
1476
+ // Check if the package exists in the embedded gs/ directory
1477
+ metaFilePath := filepath.Join("gs", pkgPath, "meta.json")
1478
+ _, err := goscript.GsOverrides.ReadFile(metaFilePath)
1479
+ return err == nil
1480
+ }
1481
+
1376
1482
  // IsMethodAsync checks if a method call is async based on package metadata
1377
1483
  func (a *Analysis) IsMethodAsync(pkgPath, typeName, methodName string) bool {
1378
1484
  // First, check pre-computed method async status
@@ -2147,13 +2253,49 @@ func (v *analysisVisitor) analyzeAllMethodsAsync() {
2147
2253
  // Initialize visitingMethods map
2148
2254
  v.visitingMethods = make(map[MethodKey]bool)
2149
2255
 
2150
- // Analyze methods in current package
2151
- v.analyzePackageMethodsAsync(v.pkg)
2256
+ // Fixed-point iteration: keep analyzing until nothing changes
2257
+ // This handles cases where method A calls method B, but B is analyzed after A
2258
+ maxIterations := 10
2259
+ for iteration := 0; iteration < maxIterations; iteration++ {
2260
+ // Clear visitingMethods map for this iteration
2261
+ v.visitingMethods = make(map[MethodKey]bool)
2152
2262
 
2153
- // Analyze methods in all dependency packages
2154
- for _, pkg := range v.analysis.AllPackages {
2155
- if pkg != v.pkg {
2156
- v.analyzePackageMethodsAsync(pkg)
2263
+ // Track if anything changed in this iteration
2264
+ changed := false
2265
+
2266
+ // Save previous state
2267
+ previousState := make(map[MethodKey]bool)
2268
+ for k, v := range v.analysis.MethodAsyncStatus {
2269
+ previousState[k] = v
2270
+ }
2271
+
2272
+ // Re-analyze methods in current package
2273
+ v.analyzePackageMethodsAsync(v.pkg)
2274
+
2275
+ // Re-analyze methods in all dependency packages
2276
+ for _, pkg := range v.analysis.AllPackages {
2277
+ if pkg != v.pkg {
2278
+ v.analyzePackageMethodsAsync(pkg)
2279
+ }
2280
+ }
2281
+
2282
+ // Check if anything changed
2283
+ for k, newValue := range v.analysis.MethodAsyncStatus {
2284
+ oldValue, existed := previousState[k]
2285
+ if !existed {
2286
+ // New method added - check if it's async (if sync, no need to re-analyze dependents)
2287
+ if newValue {
2288
+ changed = true
2289
+ }
2290
+ } else if oldValue != newValue {
2291
+ // Method changed from sync to async (or vice versa)
2292
+ changed = true
2293
+ }
2294
+ }
2295
+
2296
+ // If nothing changed, we've reached a fixed point
2297
+ if !changed {
2298
+ break
2157
2299
  }
2158
2300
  }
2159
2301
 
@@ -2212,11 +2354,6 @@ func (v *analysisVisitor) analyzeFunctionLiteralAsync(funcLit *ast.FuncLit, pkg
2212
2354
  func (v *analysisVisitor) analyzeMethodAsync(funcDecl *ast.FuncDecl, pkg *packages.Package) {
2213
2355
  methodKey := v.getMethodKey(funcDecl, pkg)
2214
2356
 
2215
- // Check if already analyzed
2216
- if _, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2217
- return
2218
- }
2219
-
2220
2357
  // Check for cycles
2221
2358
  if v.visitingMethods[methodKey] {
2222
2359
  // Cycle detected, assume sync to break recursion
@@ -2230,18 +2367,32 @@ func (v *analysisVisitor) analyzeMethodAsync(funcDecl *ast.FuncDecl, pkg *packag
2230
2367
  // Determine if method is async
2231
2368
  isAsync := false
2232
2369
 
2233
- // Determine if this is a truly external package vs a package being compiled locally
2234
- isExternalPackage := pkg.Types != v.pkg.Types && v.analysis.AllPackages[pkg.Types.Path()] == nil
2370
+ // Determine if this is a handwritten package (from gs/ directory)
2371
+ // Handwritten packages should not have their bodies analyzed
2372
+ isHandwrittenPackage := v.analysis.isHandwrittenPackage(methodKey.PackagePath)
2235
2373
 
2236
- if isExternalPackage {
2237
- // Truly external package: check metadata first, fall back to body analysis
2238
- isAsync = v.checkExternalMethodMetadata(methodKey.PackagePath, methodKey.ReceiverType, methodKey.MethodName)
2239
- } else {
2240
- // Local package or package being compiled: analyze method body
2241
- if funcDecl.Body != nil {
2242
- isAsync = v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2374
+ if isHandwrittenPackage {
2375
+ // For handwritten packages, check if we have pre-loaded metadata
2376
+ metadataKey := MethodKey{
2377
+ PackagePath: methodKey.PackagePath,
2378
+ ReceiverType: methodKey.ReceiverType,
2379
+ MethodName: methodKey.MethodName,
2380
+ }
2381
+ metadataIsAsync, hasMetadata := v.analysis.MethodAsyncStatus[metadataKey]
2382
+
2383
+ if hasMetadata {
2384
+ // Use explicit metadata from handwritten packages (gs/)
2385
+ isAsync = metadataIsAsync
2386
+ } else {
2387
+ // Handwritten package but no explicit metadata: assume sync
2388
+ isAsync = false
2243
2389
  }
2390
+ } else if funcDecl.Body != nil {
2391
+ // Not a handwritten package and has body: always analyze for async operations
2392
+ // This allows fixed-point iteration to update results
2393
+ isAsync = v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2244
2394
  }
2395
+ // Otherwise leave isAsync as false
2245
2396
 
2246
2397
  // Store result in MethodAsyncStatus
2247
2398
  v.analysis.MethodAsyncStatus[methodKey] = isAsync
@@ -2319,16 +2470,8 @@ func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *pa
2319
2470
 
2320
2471
  case *ast.CallExpr:
2321
2472
  // Check if we're calling a function known to be async
2322
- isCallAsyncResult := v.isCallAsync(s, pkg)
2323
- if isCallAsyncResult {
2473
+ if v.isCallAsync(s, pkg) {
2324
2474
  hasAsync = true
2325
- callName := ""
2326
- if ident, ok := s.Fun.(*ast.Ident); ok {
2327
- callName = ident.Name
2328
- } else if sel, ok := s.Fun.(*ast.SelectorExpr); ok {
2329
- callName = sel.Sel.Name
2330
- }
2331
- asyncReasons = append(asyncReasons, fmt.Sprintf("async call: %s", callName))
2332
2475
  return false
2333
2476
  }
2334
2477
  }
@@ -2459,21 +2602,19 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
2459
2602
  return status
2460
2603
  }
2461
2604
 
2462
- // Only try to analyze methods for packages that don't have metadata loaded
2463
- // If a package has metadata, we should rely solely on that metadata
2464
- if targetPkg := v.analysis.AllPackages[methodPkgPath]; targetPkg != nil {
2465
- // Check if this package has metadata loaded by checking if any method from this package
2466
- // exists in MethodAsyncStatus. If so, don't analyze - rely on metadata only.
2467
- hasMetadata := false
2468
- for key := range v.analysis.MethodAsyncStatus {
2469
- if key.PackagePath == methodPkgPath {
2470
- hasMetadata = true
2471
- break
2605
+ // Check if this is a method in the same package we're currently analyzing
2606
+ if methodPkgPath == v.pkg.Types.Path() {
2607
+ // This is a method in the same package - we should analyze it if we haven't yet
2608
+ if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), v.pkg); funcDecl != nil {
2609
+ v.analyzeMethodAsync(funcDecl, v.pkg)
2610
+ if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2611
+ return status
2472
2612
  }
2473
2613
  }
2474
-
2475
- // Only analyze if no metadata exists for this package
2476
- if !hasMetadata {
2614
+ } else {
2615
+ // For methods in other packages that we're compiling together
2616
+ if targetPkg := v.analysis.AllPackages[methodPkgPath]; targetPkg != nil {
2617
+ // Try to analyze the method if we haven't already
2477
2618
  if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), targetPkg); funcDecl != nil {
2478
2619
  v.analyzeMethodAsync(funcDecl, targetPkg)
2479
2620
  if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {