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.
@@ -9,8 +9,8 @@ import (
9
9
  "github.com/pkg/errors"
10
10
  "github.com/sirupsen/logrus"
11
11
 
12
- // _ ensure we include the builtin package
13
- _ "github.com/aperturerobotics/goscript/builtin"
12
+ // _ ensure we include the gs package
13
+ _ "github.com/aperturerobotics/goscript"
14
14
  )
15
15
 
16
16
  var (
@@ -41,6 +41,21 @@ type VariableUsageInfo struct {
41
41
  Destinations []AssignmentInfo
42
42
  }
43
43
 
44
+ // FunctionInfo consolidates function-related tracking data.
45
+ type FunctionInfo struct {
46
+ IsAsync bool
47
+ NamedReturns []string
48
+ }
49
+
50
+ // NodeInfo consolidates node-related tracking data.
51
+ type NodeInfo struct {
52
+ NeedsDefer bool
53
+ InAsyncContext bool
54
+ IsBareReturn bool
55
+ EnclosingFuncDecl *ast.FuncDecl
56
+ EnclosingFuncLit *ast.FuncLit
57
+ }
58
+
44
59
  // Analysis holds information gathered during the analysis phase of the Go code compilation.
45
60
  // This data is used to make decisions about how to generate TypeScript code.
46
61
  // Analysis is read-only after being built and should not be modified during code generation.
@@ -55,25 +70,25 @@ type Analysis struct {
55
70
  // Cmap stores the comment map for the file
56
71
  Cmap ast.CommentMap
57
72
 
58
- // AsyncFuncs tracks which functions are async using the function's types.Object
59
- // as the key to avoid false-positive matches with functions having the same name
60
- AsyncFuncs map[types.Object]bool
73
+ // FunctionData consolidates function-related tracking into one map
74
+ FunctionData map[types.Object]*FunctionInfo
61
75
 
62
- // NeedsDeferMap tracks nodes that need defer handling
63
- NeedsDeferMap map[ast.Node]bool
76
+ // NodeData consolidates node-related tracking into one map
77
+ NodeData map[ast.Node]*NodeInfo
64
78
 
65
- // IsInAsyncFunctionMap tracks nodes that are inside async functions
66
- IsInAsyncFunctionMap map[ast.Node]bool
79
+ // Keep specialized maps that serve different purposes
80
+ // FuncLitData tracks function literal specific data since they don't have types.Object
81
+ FuncLitData map[*ast.FuncLit]*FunctionInfo
67
82
  }
68
83
 
69
84
  // NewAnalysis creates a new Analysis instance.
70
85
  func NewAnalysis() *Analysis {
71
86
  return &Analysis{
72
- VariableUsage: make(map[types.Object]*VariableUsageInfo),
73
- Imports: make(map[string]*fileImport),
74
- AsyncFuncs: make(map[types.Object]bool),
75
- NeedsDeferMap: make(map[ast.Node]bool),
76
- IsInAsyncFunctionMap: make(map[ast.Node]bool),
87
+ VariableUsage: make(map[types.Object]*VariableUsageInfo),
88
+ Imports: make(map[string]*fileImport),
89
+ FunctionData: make(map[types.Object]*FunctionInfo),
90
+ NodeData: make(map[ast.Node]*NodeInfo),
91
+ FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
77
92
  }
78
93
  }
79
94
 
@@ -82,7 +97,11 @@ func (a *Analysis) NeedsDefer(node ast.Node) bool {
82
97
  if node == nil {
83
98
  return false
84
99
  }
85
- return a.NeedsDeferMap[node]
100
+ nodeInfo := a.NodeData[node]
101
+ if nodeInfo == nil {
102
+ return false
103
+ }
104
+ return nodeInfo.NeedsDefer
86
105
  }
87
106
 
88
107
  // IsInAsyncFunction returns whether the given node is inside an async function.
@@ -90,7 +109,11 @@ func (a *Analysis) IsInAsyncFunction(node ast.Node) bool {
90
109
  if node == nil {
91
110
  return false
92
111
  }
93
- return a.IsInAsyncFunctionMap[node]
112
+ nodeInfo := a.NodeData[node]
113
+ if nodeInfo == nil {
114
+ return false
115
+ }
116
+ return nodeInfo.InAsyncContext
94
117
  }
95
118
 
96
119
  // IsAsyncFunc returns whether the given object represents an async function.
@@ -98,7 +121,11 @@ func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
98
121
  if obj == nil {
99
122
  return false
100
123
  }
101
- return a.AsyncFuncs[obj]
124
+ funcInfo := a.FunctionData[obj]
125
+ if funcInfo == nil {
126
+ return false
127
+ }
128
+ return funcInfo.IsAsync
102
129
  }
103
130
 
104
131
  // IsFuncLitAsync checks if a function literal is async based on our analysis.
@@ -106,8 +133,16 @@ func (a *Analysis) IsFuncLitAsync(funcLit *ast.FuncLit) bool {
106
133
  if funcLit == nil {
107
134
  return false
108
135
  }
109
- // Function literals are marked during analysis if they contain async operations
110
- return a.IsInAsyncFunctionMap[funcLit]
136
+ // Check function literal specific data first
137
+ if funcInfo := a.FuncLitData[funcLit]; funcInfo != nil {
138
+ return funcInfo.IsAsync
139
+ }
140
+ // Fall back to node data for backwards compatibility
141
+ nodeInfo := a.NodeData[funcLit]
142
+ if nodeInfo == nil {
143
+ return false
144
+ }
145
+ return nodeInfo.InAsyncContext
111
146
  }
112
147
 
113
148
  // NeedsBoxed returns whether the given object needs to be boxed.
@@ -302,6 +337,12 @@ type analysisVisitor struct {
302
337
 
303
338
  // currentFuncObj tracks the object of the function declaration we're currently analyzing
304
339
  currentFuncObj types.Object
340
+
341
+ // currentFuncDecl tracks the *ast.FuncDecl of the function we're currently analyzing.
342
+ currentFuncDecl *ast.FuncDecl
343
+
344
+ // currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
345
+ currentFuncLit *ast.FuncLit
305
346
  }
306
347
 
307
348
  // getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
@@ -324,8 +365,11 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
324
365
  return nil
325
366
  }
326
367
 
327
- // Store async state for the current node
328
- v.analysis.IsInAsyncFunctionMap[node] = v.inAsyncFunction
368
+ // Initialize and store async state for the current node
369
+ if v.analysis.NodeData[node] == nil {
370
+ v.analysis.NodeData[node] = &NodeInfo{}
371
+ }
372
+ v.analysis.NodeData[node].InAsyncContext = v.inAsyncFunction
329
373
 
330
374
  switch n := node.(type) {
331
375
  case *ast.GenDecl:
@@ -393,24 +437,40 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
393
437
  return v
394
438
 
395
439
  case *ast.FuncDecl:
396
- // Determine if this function declaration is async based on its body
440
+ // Save original states to restore after visiting
441
+ originalInAsync := v.inAsyncFunction
442
+ originalFuncObj := v.currentFuncObj
443
+ originalFuncDecl := v.currentFuncDecl
444
+ originalFuncLit := v.currentFuncLit
445
+ originalReceiver := v.currentReceiver
446
+
447
+ // Reset for current function
397
448
  v.currentFuncName = n.Name.Name
449
+ v.currentFuncDecl = n
450
+ v.currentFuncLit = nil
451
+ v.currentReceiver = nil
452
+
453
+ // Determine if this function declaration is async based on its body
398
454
  isAsync := false
399
455
  if n.Body != nil {
400
456
  containsAsyncOps := v.containsAsyncOperations(n.Body)
401
457
  if containsAsyncOps {
402
458
  // Get the object for this function declaration
403
459
  if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
404
- v.analysis.AsyncFuncs[obj] = true
460
+ v.analysis.FunctionData[obj] = &FunctionInfo{
461
+ IsAsync: true,
462
+ NamedReturns: v.getNamedReturns(n),
463
+ }
464
+ isAsync = true
405
465
  }
406
- isAsync = true
407
466
  }
408
467
  }
409
- v.analysis.IsInAsyncFunctionMap[n] = isAsync
468
+ if v.analysis.NodeData[n] == nil {
469
+ v.analysis.NodeData[n] = &NodeInfo{}
470
+ }
471
+ v.analysis.NodeData[n].InAsyncContext = isAsync
410
472
 
411
473
  // Set current receiver if this is a method
412
- originalReceiver := v.currentReceiver
413
- v.currentReceiver = nil // Reset for current function
414
474
  if n.Recv != nil && len(n.Recv.List) > 0 {
415
475
  // Assuming a single receiver for simplicity for now
416
476
  if len(n.Recv.List[0].Names) > 0 {
@@ -427,22 +487,41 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
427
487
  }
428
488
  }
429
489
 
430
- // Save original states to restore after visiting
431
- originalInAsync := v.inAsyncFunction
432
- originalFuncObj := v.currentFuncObj
490
+ // Store named return variables
491
+ if n.Type != nil && n.Type.Results != nil {
492
+ var namedReturns []string
493
+ for _, field := range n.Type.Results.List {
494
+ for _, name := range field.Names {
495
+ namedReturns = append(namedReturns, name.Name)
496
+ }
497
+ }
498
+ if len(namedReturns) > 0 {
499
+ if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
500
+ if v.analysis.FunctionData[obj] == nil {
501
+ v.analysis.FunctionData[obj] = &FunctionInfo{}
502
+ }
503
+ v.analysis.FunctionData[obj].NamedReturns = namedReturns
504
+ }
505
+ }
506
+ }
433
507
 
434
508
  // Update visitor state for this function
435
509
  v.inAsyncFunction = isAsync
436
510
  v.currentFuncObj = v.pkg.TypesInfo.ObjectOf(n.Name)
437
- v.analysis.IsInAsyncFunctionMap[n] = isAsync // Ensure FuncDecl node itself is marked
511
+ v.analysis.NodeData[n].InAsyncContext = isAsync // Ensure FuncDecl node itself is marked
438
512
 
439
- // Check if the body contains any defer statements
440
- if n.Body != nil && v.containsDefer(n.Body) {
441
- v.analysis.NeedsDeferMap[n.Body] = true
442
- }
513
+ if n.Body != nil {
514
+ // Check if the body contains any defer statements
515
+ if v.containsDefer(n.Body) {
516
+ if v.analysis.NodeData[n] == nil {
517
+ v.analysis.NodeData[n] = &NodeInfo{}
518
+ }
519
+ v.analysis.NodeData[n].NeedsDefer = true
520
+ }
443
521
 
444
- // Visit the body with updated state
445
- ast.Walk(v, n.Body)
522
+ // Visit the body with updated state
523
+ ast.Walk(v, n.Body)
524
+ }
446
525
 
447
526
  // Restore states after visiting
448
527
  defer func() {
@@ -450,21 +529,52 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
450
529
  v.inAsyncFunction = originalInAsync
451
530
  v.currentReceiver = originalReceiver
452
531
  v.currentFuncObj = originalFuncObj
532
+ v.currentFuncDecl = originalFuncDecl
533
+ v.currentFuncLit = originalFuncLit
453
534
  }()
454
535
  return nil // Stop traversal here, ast.Walk handled the body
455
536
 
456
537
  case *ast.FuncLit:
538
+ // Save original inAsyncFunction state to restore after visiting
539
+ originalInAsync := v.inAsyncFunction
540
+ originalFuncDecl := v.currentFuncDecl
541
+ originalFuncLit := v.currentFuncLit
542
+
543
+ // Set current function literal
544
+ v.currentFuncDecl = nil
545
+ v.currentFuncLit = n
546
+
457
547
  // Determine if this function literal is async based on its body
458
548
  isAsync := v.containsAsyncOperations(n.Body)
459
- v.analysis.IsInAsyncFunctionMap[n] = isAsync
549
+ if v.analysis.NodeData[n] == nil {
550
+ v.analysis.NodeData[n] = &NodeInfo{}
551
+ }
552
+ v.analysis.NodeData[n].InAsyncContext = isAsync
553
+
554
+ // Store named return variables for function literal
555
+ if n.Type != nil && n.Type.Results != nil {
556
+ var namedReturns []string
557
+ for _, field := range n.Type.Results.List {
558
+ for _, name := range field.Names {
559
+ namedReturns = append(namedReturns, name.Name)
560
+ }
561
+ }
562
+ if len(namedReturns) > 0 {
563
+ v.analysis.FuncLitData[n] = &FunctionInfo{
564
+ IsAsync: isAsync,
565
+ NamedReturns: namedReturns,
566
+ }
567
+ }
568
+ }
460
569
 
461
- // Save original inAsyncFunction state to restore after visiting
462
- originalInAsync := v.inAsyncFunction
463
570
  v.inAsyncFunction = isAsync
464
571
 
465
572
  // Check if the body contains any defer statements
466
573
  if n.Body != nil && v.containsDefer(n.Body) {
467
- v.analysis.NeedsDeferMap[n.Body] = true
574
+ if v.analysis.NodeData[n] == nil {
575
+ v.analysis.NodeData[n] = &NodeInfo{}
576
+ }
577
+ v.analysis.NodeData[n].NeedsDefer = true
468
578
  }
469
579
 
470
580
  // Visit the body with updated state
@@ -472,16 +582,27 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
472
582
 
473
583
  // Restore inAsyncFunction state after visiting
474
584
  v.inAsyncFunction = originalInAsync
585
+ v.currentFuncDecl = originalFuncDecl
586
+ v.currentFuncLit = originalFuncLit
475
587
  return nil // Stop traversal here, ast.Walk handled the body
476
588
 
477
589
  case *ast.BlockStmt:
590
+ if n == nil || len(n.List) == 0 {
591
+ break
592
+ }
593
+
594
+ // Initialize NodeData for this block
595
+ if v.analysis.NodeData[n] == nil {
596
+ v.analysis.NodeData[n] = &NodeInfo{}
597
+ }
598
+
478
599
  // Check for defer statements in this block
479
600
  if v.containsDefer(n) {
480
- v.analysis.NeedsDeferMap[n] = true
601
+ v.analysis.NodeData[n].NeedsDefer = true
481
602
  }
482
603
 
483
604
  // Store async state for this block
484
- v.analysis.IsInAsyncFunctionMap[n] = v.inAsyncFunction
605
+ v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
485
606
 
486
607
  return v
487
608
 
@@ -500,12 +621,18 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
500
621
  if obj := v.pkg.TypesInfo.Uses[funcIdent]; obj != nil && v.analysis.IsAsyncFunc(obj) {
501
622
  // We're calling an async function, so mark current function as async if we're in one
502
623
  if v.currentFuncObj != nil {
503
- v.analysis.AsyncFuncs[v.currentFuncObj] = true
624
+ v.analysis.FunctionData[v.currentFuncObj] = &FunctionInfo{
625
+ IsAsync: true,
626
+ NamedReturns: v.getNamedReturns(v.currentFuncDecl),
627
+ }
504
628
  v.inAsyncFunction = true // Update visitor state
505
629
  // Mark the FuncDecl node itself if possible (might need to store the node too)
506
- for nodeAst := range v.analysis.IsInAsyncFunctionMap { // Find the node to update
630
+ for nodeAst := range v.analysis.NodeData { // Find the node to update
507
631
  if fd, ok := nodeAst.(*ast.FuncDecl); ok && v.pkg.TypesInfo.ObjectOf(fd.Name) == v.currentFuncObj {
508
- v.analysis.IsInAsyncFunctionMap[nodeAst] = true
632
+ if v.analysis.NodeData[nodeAst] == nil {
633
+ v.analysis.NodeData[nodeAst] = &NodeInfo{}
634
+ }
635
+ v.analysis.NodeData[nodeAst].InAsyncContext = true
509
636
  }
510
637
  }
511
638
  }
@@ -513,7 +640,10 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
513
640
  }
514
641
 
515
642
  // Store async state for this call expression
516
- v.analysis.IsInAsyncFunctionMap[n] = v.inAsyncFunction
643
+ if v.analysis.NodeData[n] == nil {
644
+ v.analysis.NodeData[n] = &NodeInfo{}
645
+ }
646
+ v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
517
647
 
518
648
  return v
519
649
 
@@ -610,14 +740,46 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
610
740
  }
611
741
  return v // Continue traversal
612
742
 
613
- case *ast.CompositeLit:
614
- // No need to track private field access in composite literals since all fields are public
615
- return v
743
+ case *ast.ReturnStmt:
744
+ // Initialize NodeData for return statement
745
+ if v.analysis.NodeData[n] == nil {
746
+ v.analysis.NodeData[n] = &NodeInfo{}
747
+ }
616
748
 
617
- default:
618
- // For all other nodes, continue traversal
619
- return v
749
+ // Record the enclosing function/literal for this return statement
750
+ if v.currentFuncDecl != nil {
751
+ v.analysis.NodeData[n].EnclosingFuncDecl = v.currentFuncDecl
752
+ } else if v.currentFuncLit != nil {
753
+ v.analysis.NodeData[n].EnclosingFuncLit = v.currentFuncLit
754
+ }
755
+
756
+ // Check if it's a bare return
757
+ if len(n.Results) == 0 {
758
+ if v.currentFuncDecl != nil {
759
+ // Check if the enclosing function declaration has named returns
760
+ if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
761
+ if _, ok := v.analysis.FunctionData[obj]; ok {
762
+ if v.analysis.NodeData[n] == nil {
763
+ v.analysis.NodeData[n] = &NodeInfo{}
764
+ }
765
+ v.analysis.NodeData[n].IsBareReturn = true
766
+ }
767
+ }
768
+ } else if v.currentFuncLit != nil {
769
+ // Check if the enclosing function literal has named returns
770
+ if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
771
+ if v.analysis.NodeData[n] == nil {
772
+ v.analysis.NodeData[n] = &NodeInfo{}
773
+ }
774
+ v.analysis.NodeData[n].IsBareReturn = true
775
+ }
776
+ }
777
+ }
778
+ return v // Continue traversal
620
779
  }
780
+
781
+ // For all other nodes, continue traversal
782
+ return v
621
783
  }
622
784
 
623
785
  // containsAsyncOperations checks if a node contains any async operations like channel operations.
@@ -666,6 +828,9 @@ func (v *analysisVisitor) containsDefer(block *ast.BlockStmt) bool {
666
828
  hasDefer := false
667
829
 
668
830
  ast.Inspect(block, func(n ast.Node) bool {
831
+ if n == nil {
832
+ return true
833
+ }
669
834
  if _, ok := n.(*ast.DeferStmt); ok {
670
835
  hasDefer = true
671
836
  return false
@@ -724,3 +889,16 @@ func AnalyzeFile(file *ast.File, pkg *packages.Package, analysis *Analysis, cmap
724
889
  // Walk the AST with our visitor
725
890
  ast.Walk(visitor, file)
726
891
  }
892
+
893
+ // getNamedReturns retrieves the named returns for a function
894
+ func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
895
+ var namedReturns []string
896
+ if funcDecl.Type != nil && funcDecl.Type.Results != nil {
897
+ for _, field := range funcDecl.Type.Results.List {
898
+ for _, name := range field.Names {
899
+ namedReturns = append(namedReturns, name.Name)
900
+ }
901
+ }
902
+ }
903
+ return namedReturns
904
+ }