goscript 0.0.48 → 0.0.50

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 (159) hide show
  1. package/cmd/goscript/deps.go +1 -4
  2. package/compiler/analysis.go +1120 -513
  3. package/compiler/analysis_test.go +113 -4
  4. package/compiler/compiler.go +88 -124
  5. package/compiler/decl.go +22 -0
  6. package/compiler/expr-call-async.go +46 -52
  7. package/compiler/expr-call-type-conversion.go +144 -59
  8. package/compiler/expr-call.go +235 -12
  9. package/compiler/expr.go +5 -82
  10. package/compiler/gs_dependencies_test.go +60 -1
  11. package/compiler/spec-value.go +73 -51
  12. package/compiler/spec.go +337 -151
  13. package/compiler/stmt-assign.go +7 -4
  14. package/compiler/stmt.go +250 -81
  15. package/compiler/type.go +13 -0
  16. package/dist/gs/builtin/builtin.d.ts +1 -5
  17. package/dist/gs/builtin/builtin.js +1 -34
  18. package/dist/gs/builtin/builtin.js.map +1 -1
  19. package/dist/gs/builtin/slice.js.map +1 -1
  20. package/dist/gs/context/context.d.ts +16 -18
  21. package/dist/gs/context/context.js +23 -13
  22. package/dist/gs/context/context.js.map +1 -1
  23. package/dist/gs/fmt/fmt.js +22 -4
  24. package/dist/gs/fmt/fmt.js.map +1 -1
  25. package/dist/gs/io/fs/fs.d.ts +6 -12
  26. package/dist/gs/io/fs/fs.js +52 -67
  27. package/dist/gs/io/fs/fs.js.map +1 -1
  28. package/dist/gs/os/index.d.ts +2 -1
  29. package/dist/gs/os/index.js +1 -1
  30. package/dist/gs/os/index.js.map +1 -1
  31. package/dist/gs/os/types_js.gs.d.ts +7 -1
  32. package/dist/gs/os/types_js.gs.js +16 -1
  33. package/dist/gs/os/types_js.gs.js.map +1 -1
  34. package/dist/gs/os/types_unix.gs.js +2 -2
  35. package/dist/gs/os/types_unix.gs.js.map +1 -1
  36. package/dist/gs/reflect/index.d.ts +3 -3
  37. package/dist/gs/reflect/index.js +2 -2
  38. package/dist/gs/reflect/index.js.map +1 -1
  39. package/dist/gs/reflect/map.js +2 -2
  40. package/dist/gs/reflect/map.js.map +1 -1
  41. package/dist/gs/reflect/type.d.ts +8 -9
  42. package/dist/gs/reflect/type.js +98 -103
  43. package/dist/gs/reflect/type.js.map +1 -1
  44. package/dist/gs/reflect/types.d.ts +1 -10
  45. package/dist/gs/reflect/types.js +3 -26
  46. package/dist/gs/reflect/types.js.map +1 -1
  47. package/dist/gs/reflect/value.js +23 -23
  48. package/dist/gs/reflect/value.js.map +1 -1
  49. package/dist/gs/reflect/visiblefields.js +3 -3
  50. package/dist/gs/reflect/visiblefields.js.map +1 -1
  51. package/dist/gs/time/time.d.ts +13 -23
  52. package/dist/gs/time/time.js +57 -75
  53. package/dist/gs/time/time.js.map +1 -1
  54. package/gs/builtin/builtin.ts +3 -47
  55. package/gs/builtin/slice.ts +1 -1
  56. package/gs/bytes/meta.json +10 -0
  57. package/gs/context/context.ts +63 -45
  58. package/gs/fmt/fmt.ts +22 -4
  59. package/gs/fmt/meta.json +5 -0
  60. package/gs/internal/meta.json +5 -0
  61. package/gs/io/fs/fs.ts +58 -73
  62. package/gs/io/meta.json +9 -0
  63. package/gs/maps/meta.json +6 -0
  64. package/gs/math/meta.json +5 -0
  65. package/gs/os/index.ts +8 -1
  66. package/gs/os/meta.json +15 -0
  67. package/gs/os/types_js.gs.ts +22 -1
  68. package/gs/os/types_unix.gs.ts +2 -2
  69. package/gs/path/meta.json +6 -0
  70. package/gs/reflect/function-types.test.ts +10 -10
  71. package/gs/reflect/index.ts +6 -6
  72. package/gs/reflect/map.ts +2 -2
  73. package/gs/reflect/meta.json +5 -0
  74. package/gs/reflect/type.ts +105 -105
  75. package/gs/reflect/types.ts +2 -28
  76. package/gs/reflect/value.ts +23 -23
  77. package/gs/reflect/visiblefields.ts +3 -3
  78. package/gs/strconv/meta.json +5 -0
  79. package/gs/strings/meta.json +9 -0
  80. package/gs/sync/meta.json +19 -0
  81. package/gs/time/time.ts +65 -84
  82. package/package.json +2 -2
  83. package/dist/gs/builtin/io.d.ts +0 -16
  84. package/dist/gs/builtin/io.js +0 -15
  85. package/dist/gs/builtin/io.js.map +0 -1
  86. package/dist/gs/internal/testlog/index.d.ts +0 -1
  87. package/dist/gs/internal/testlog/index.js +0 -5
  88. package/dist/gs/internal/testlog/index.js.map +0 -1
  89. package/dist/gs/maps/iter.gs.d.ts +0 -7
  90. package/dist/gs/maps/iter.gs.js +0 -65
  91. package/dist/gs/maps/iter.gs.js.map +0 -1
  92. package/dist/gs/maps/maps.gs.d.ts +0 -7
  93. package/dist/gs/maps/maps.gs.js +0 -79
  94. package/dist/gs/maps/maps.gs.js.map +0 -1
  95. package/dist/gs/reflect/abi.d.ts +0 -59
  96. package/dist/gs/reflect/abi.gs.d.ts +0 -59
  97. package/dist/gs/reflect/abi.gs.js +0 -79
  98. package/dist/gs/reflect/abi.gs.js.map +0 -1
  99. package/dist/gs/reflect/abi.js +0 -79
  100. package/dist/gs/reflect/abi.js.map +0 -1
  101. package/dist/gs/reflect/badlinkname.d.ts +0 -52
  102. package/dist/gs/reflect/badlinkname.gs.d.ts +0 -52
  103. package/dist/gs/reflect/badlinkname.gs.js +0 -72
  104. package/dist/gs/reflect/badlinkname.gs.js.map +0 -1
  105. package/dist/gs/reflect/badlinkname.js +0 -72
  106. package/dist/gs/reflect/badlinkname.js.map +0 -1
  107. package/dist/gs/reflect/deepequal.gs.d.ts +0 -25
  108. package/dist/gs/reflect/deepequal.gs.js +0 -308
  109. package/dist/gs/reflect/deepequal.gs.js.map +0 -1
  110. package/dist/gs/reflect/float32reg_generic.gs.d.ts +0 -2
  111. package/dist/gs/reflect/float32reg_generic.gs.js +0 -10
  112. package/dist/gs/reflect/float32reg_generic.gs.js.map +0 -1
  113. package/dist/gs/reflect/index.gs.d.ts +0 -1
  114. package/dist/gs/reflect/index.gs.js +0 -3
  115. package/dist/gs/reflect/index.gs.js.map +0 -1
  116. package/dist/gs/reflect/iter.gs.d.ts +0 -3
  117. package/dist/gs/reflect/iter.gs.js +0 -24
  118. package/dist/gs/reflect/iter.gs.js.map +0 -1
  119. package/dist/gs/reflect/makefunc.gs.d.ts +0 -34
  120. package/dist/gs/reflect/makefunc.gs.js +0 -288
  121. package/dist/gs/reflect/makefunc.gs.js.map +0 -1
  122. package/dist/gs/reflect/map_swiss.gs.d.ts +0 -14
  123. package/dist/gs/reflect/map_swiss.gs.js +0 -70
  124. package/dist/gs/reflect/map_swiss.gs.js.map +0 -1
  125. package/dist/gs/reflect/reflect.gs.d.ts +0 -132
  126. package/dist/gs/reflect/reflect.gs.js +0 -437
  127. package/dist/gs/reflect/reflect.gs.js.map +0 -1
  128. package/dist/gs/reflect/swapper.gs.d.ts +0 -1
  129. package/dist/gs/reflect/swapper.gs.js +0 -32
  130. package/dist/gs/reflect/swapper.gs.js.map +0 -1
  131. package/dist/gs/reflect/type.gs.d.ts +0 -4
  132. package/dist/gs/reflect/type.gs.js +0 -21
  133. package/dist/gs/reflect/type.gs.js.map +0 -1
  134. package/dist/gs/reflect/value.gs.d.ts +0 -4
  135. package/dist/gs/reflect/value.gs.js +0 -12
  136. package/dist/gs/reflect/value.gs.js.map +0 -1
  137. package/dist/gs/reflect/visiblefields.gs.d.ts +0 -3
  138. package/dist/gs/reflect/visiblefields.gs.js +0 -123
  139. package/dist/gs/reflect/visiblefields.gs.js.map +0 -1
  140. package/dist/gs/stringslite/index.d.ts +0 -1
  141. package/dist/gs/stringslite/index.js +0 -2
  142. package/dist/gs/stringslite/index.js.map +0 -1
  143. package/dist/gs/stringslite/strings.d.ts +0 -11
  144. package/dist/gs/stringslite/strings.js +0 -67
  145. package/dist/gs/stringslite/strings.js.map +0 -1
  146. package/gs/bytes/metadata.go +0 -12
  147. package/gs/fmt/metadata.go +0 -7
  148. package/gs/internal/metadata.go +0 -7
  149. package/gs/io/io.go +0 -75
  150. package/gs/io/metadata.go +0 -11
  151. package/gs/maps/metadata.go +0 -8
  152. package/gs/math/metadata.go +0 -7
  153. package/gs/os/metadata.go +0 -17
  154. package/gs/path/metadata.go +0 -8
  155. package/gs/reflect/metadata.go +0 -7
  156. package/gs/strconv/metadata.go +0 -7
  157. package/gs/strings/metadata.go +0 -11
  158. package/gs/sync/metadata.go +0 -7
  159. package/gs/sync/sync.go +0 -64
@@ -1,12 +1,14 @@
1
1
  package compiler
2
2
 
3
3
  import (
4
+ "encoding/json"
4
5
  "go/ast"
5
6
  "go/token"
6
7
  "go/types"
7
8
  "path/filepath"
8
9
  "strings"
9
10
 
11
+ "github.com/aperturerobotics/goscript"
10
12
  "golang.org/x/tools/go/packages"
11
13
  )
12
14
 
@@ -81,6 +83,38 @@ type NodeInfo struct {
81
83
  IsInsideFunction bool // true if this declaration is inside a function body
82
84
  IsMethodValue bool // true if this SelectorExpr is a method value that needs binding
83
85
  ShadowingInfo *ShadowingInfo // variable shadowing information for if statements
86
+ IdentifierMapping string // replacement name for this identifier (e.g., receiver -> "receiver")
87
+ }
88
+
89
+ // PackageMetadataKey represents a key for looking up package metadata
90
+ type PackageMetadataKey struct {
91
+ PackagePath string // Full package path (e.g., "github.com/aperturerobotics/util/csync")
92
+ TypeName string // Type name (e.g., "Mutex")
93
+ MethodName string // Method name (e.g., "Lock")
94
+ }
95
+
96
+ // MethodMetadata represents metadata about a method
97
+ type MethodMetadata struct {
98
+ IsAsync bool // Whether the method is async
99
+ }
100
+
101
+ // GsMetadata represents the structure of a meta.json file in gs/ packages
102
+ type GsMetadata struct {
103
+ Dependencies []string `json:"dependencies,omitempty"`
104
+ AsyncMethods map[string]bool `json:"asyncMethods,omitempty"`
105
+ }
106
+
107
+ // InterfaceMethodKey uniquely identifies an interface method
108
+ type InterfaceMethodKey struct {
109
+ InterfaceType string // The string representation of the interface type
110
+ MethodName string // The method name
111
+ }
112
+
113
+ // ImplementationInfo tracks information about a struct that implements an interface method
114
+ type ImplementationInfo struct {
115
+ StructType *types.Named // The struct type that implements the interface
116
+ Method *types.Func // The method object
117
+ IsAsyncByFlow bool // Whether this implementation is async based on control flow analysis
84
118
  }
85
119
 
86
120
  // Analysis holds information gathered during the analysis phase of the Go code compilation.
@@ -113,8 +147,24 @@ type Analysis struct {
113
147
  // FunctionAssignments tracks which function literals are assigned to which variables
114
148
  FunctionAssignments map[types.Object]ast.Node
115
149
 
116
- // PackageMetadata holds package-level metadata
117
- PackageMetadata map[string]interface{}
150
+ // PackageMetadata holds package-level metadata with structured keys
151
+ PackageMetadata map[PackageMetadataKey]MethodMetadata
152
+
153
+ // NamedBasicTypes tracks types that should be implemented as type aliases with standalone functions
154
+ // This includes named types with basic underlying types (like uint32, string) that have methods
155
+ NamedBasicTypes map[types.Type]bool
156
+
157
+ // AllPackages stores all loaded packages for dependency analysis
158
+ // Key: package path, Value: package data
159
+ AllPackages map[string]*packages.Package
160
+
161
+ // InterfaceImplementations tracks which struct types implement which interface methods
162
+ // This is used to determine interface method async status based on implementations
163
+ InterfaceImplementations map[InterfaceMethodKey][]ImplementationInfo
164
+
165
+ // InterfaceMethodAsyncStatus caches the async status determination for interface methods
166
+ // This is computed once during analysis and reused during code generation
167
+ InterfaceMethodAsyncStatus map[InterfaceMethodKey]bool
118
168
  }
119
169
 
120
170
  // PackageAnalysis holds cross-file analysis data for a package
@@ -137,16 +187,24 @@ type PackageAnalysis struct {
137
187
  }
138
188
 
139
189
  // NewAnalysis creates a new Analysis instance.
140
- func NewAnalysis() *Analysis {
190
+ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
191
+ if allPackages == nil {
192
+ allPackages = make(map[string]*packages.Package)
193
+ }
194
+
141
195
  return &Analysis{
142
- VariableUsage: make(map[types.Object]*VariableUsageInfo),
143
- Imports: make(map[string]*fileImport),
144
- FunctionData: make(map[types.Object]*FunctionInfo),
145
- NodeData: make(map[ast.Node]*NodeInfo),
146
- FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
147
- ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
148
- FunctionAssignments: make(map[types.Object]ast.Node),
149
- PackageMetadata: make(map[string]interface{}),
196
+ VariableUsage: make(map[types.Object]*VariableUsageInfo),
197
+ Imports: make(map[string]*fileImport),
198
+ FunctionData: make(map[types.Object]*FunctionInfo),
199
+ NodeData: make(map[ast.Node]*NodeInfo),
200
+ FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
201
+ ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
202
+ FunctionAssignments: make(map[types.Object]ast.Node),
203
+ PackageMetadata: make(map[PackageMetadataKey]MethodMetadata),
204
+ NamedBasicTypes: make(map[types.Type]bool),
205
+ AllPackages: allPackages,
206
+ InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
207
+ InterfaceMethodAsyncStatus: make(map[InterfaceMethodKey]bool),
150
208
  }
151
209
  }
152
210
 
@@ -160,6 +218,28 @@ func NewPackageAnalysis() *PackageAnalysis {
160
218
  }
161
219
  }
162
220
 
221
+ // ensureNodeData ensures that NodeData exists for a given node and returns it
222
+ func (a *Analysis) ensureNodeData(node ast.Node) *NodeInfo {
223
+ if node == nil {
224
+ return nil
225
+ }
226
+ if a.NodeData[node] == nil {
227
+ a.NodeData[node] = &NodeInfo{}
228
+ }
229
+ return a.NodeData[node]
230
+ }
231
+
232
+ // ensureFunctionData ensures that FunctionData exists for a given object and returns it
233
+ func (a *Analysis) ensureFunctionData(obj types.Object) *FunctionInfo {
234
+ if obj == nil {
235
+ return nil
236
+ }
237
+ if a.FunctionData[obj] == nil {
238
+ a.FunctionData[obj] = &FunctionInfo{}
239
+ }
240
+ return a.FunctionData[obj]
241
+ }
242
+
163
243
  // NeedsDefer returns whether the given node needs defer handling.
164
244
  func (a *Analysis) NeedsDefer(node ast.Node) bool {
165
245
  if node == nil {
@@ -373,10 +453,8 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
373
453
  }
374
454
 
375
455
  // Initialize and store async state for the current node
376
- if v.analysis.NodeData[node] == nil {
377
- v.analysis.NodeData[node] = &NodeInfo{}
378
- }
379
- v.analysis.NodeData[node].InAsyncContext = v.inAsyncFunction
456
+ nodeInfo := v.analysis.ensureNodeData(node)
457
+ nodeInfo.InAsyncContext = v.inAsyncFunction
380
458
 
381
459
  switch n := node.(type) {
382
460
  case *ast.GenDecl:
@@ -449,441 +527,365 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
449
527
  }
450
528
  }
451
529
  }
452
- // Continue traversal AFTER processing the declaration itself
453
- // to handle expressions within initial values if needed.
454
- // However, the core usage tracking is done above.
455
- // Let standard traversal handle children.
456
530
  return v
457
531
 
458
532
  case *ast.FuncDecl:
459
- // Save original states to restore after visiting
460
- originalInAsync := v.inAsyncFunction
461
- originalFuncObj := v.currentFuncObj
462
- originalFuncDecl := v.currentFuncDecl
463
- originalFuncLit := v.currentFuncLit
464
- originalReceiver := v.currentReceiver
465
-
466
- // Reset for current function
467
- v.currentFuncName = n.Name.Name
468
- v.currentFuncDecl = n
469
- v.currentFuncLit = nil
470
- v.currentReceiver = nil
471
-
472
- // Determine if this function declaration is async based on its body
473
- isAsync := false
474
- if n.Body != nil {
475
- containsAsyncOps := v.containsAsyncOperations(n.Body)
476
- if containsAsyncOps {
477
- // Get the object for this function declaration
478
- if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
479
- v.analysis.FunctionData[obj] = &FunctionInfo{
480
- IsAsync: true,
481
- NamedReturns: v.getNamedReturns(n),
482
- }
483
- isAsync = true
484
- }
533
+ return v.visitFuncDecl(n)
534
+
535
+ case *ast.FuncLit:
536
+ return v.visitFuncLit(n)
537
+
538
+ case *ast.BlockStmt:
539
+ return v.visitBlockStmt(n)
540
+
541
+ case *ast.UnaryExpr:
542
+ // We handle address-of (&) within AssignStmt where it's actually used.
543
+ return v
544
+
545
+ case *ast.CallExpr:
546
+ return v.visitCallExpr(n)
547
+
548
+ case *ast.SelectorExpr:
549
+ return v.visitSelectorExpr(n)
550
+
551
+ case *ast.AssignStmt:
552
+ return v.visitAssignStmt(n)
553
+
554
+ case *ast.ReturnStmt:
555
+ return v.visitReturnStmt(n)
556
+
557
+ case *ast.DeclStmt:
558
+ return v.visitDeclStmt(n)
559
+
560
+ case *ast.IfStmt:
561
+ return v.visitIfStmt(n)
562
+
563
+ case *ast.TypeAssertExpr:
564
+ return v.visitTypeAssertExpr(n)
565
+ }
566
+
567
+ // For all other nodes, continue traversal
568
+ return v
569
+ }
570
+
571
+ // visitFuncDecl handles function declaration analysis
572
+ func (v *analysisVisitor) visitFuncDecl(n *ast.FuncDecl) ast.Visitor {
573
+ // Save original states to restore after visiting
574
+ originalInAsync := v.inAsyncFunction
575
+ originalFuncObj := v.currentFuncObj
576
+ originalFuncDecl := v.currentFuncDecl
577
+ originalFuncLit := v.currentFuncLit
578
+ originalReceiver := v.currentReceiver
579
+
580
+ // Reset for current function
581
+ v.currentFuncName = n.Name.Name
582
+ v.currentFuncDecl = n
583
+ v.currentFuncLit = nil
584
+ v.currentReceiver = nil
585
+
586
+ // Determine if this function declaration is async based on its body
587
+ isAsync := false
588
+ if n.Body != nil {
589
+ containsAsyncOps := v.containsAsyncOperations(n.Body)
590
+ if containsAsyncOps {
591
+ // Get the object for this function declaration
592
+ if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
593
+ funcInfo := v.analysis.ensureFunctionData(obj)
594
+ funcInfo.IsAsync = true
595
+ funcInfo.NamedReturns = v.getNamedReturns(n)
596
+ isAsync = true
485
597
  }
486
598
  }
487
- if v.analysis.NodeData[n] == nil {
488
- v.analysis.NodeData[n] = &NodeInfo{}
489
- }
490
- v.analysis.NodeData[n].InAsyncContext = isAsync
491
-
492
- // Set current receiver if this is a method
493
- if n.Recv != nil && len(n.Recv.List) > 0 {
494
- // Assuming a single receiver for simplicity for now
495
- if len(n.Recv.List[0].Names) > 0 {
496
- if ident := n.Recv.List[0].Names[0]; ident != nil && ident.Name != "_" {
497
- if def := v.pkg.TypesInfo.Defs[ident]; def != nil {
498
- if vr, ok := def.(*types.Var); ok {
499
- v.currentReceiver = vr
500
- // Add the receiver variable to the VariableUsage map
501
- // to ensure it is properly analyzed for varRefing
502
- v.getOrCreateUsageInfo(v.currentReceiver)
503
-
504
- // Check if receiver is used in method body
505
- receiverUsed := false
506
- if n.Body != nil {
507
- if v.isInterfaceMethod(n) {
508
- receiverUsed = true
509
- } else {
510
- receiverUsed = v.containsReceiverUsage(n.Body, vr)
511
- }
599
+ }
600
+ nodeInfo := v.analysis.ensureNodeData(n)
601
+ nodeInfo.InAsyncContext = isAsync
602
+
603
+ // Set current receiver if this is a method
604
+ if n.Recv != nil && len(n.Recv.List) > 0 {
605
+ // Assuming a single receiver for simplicity for now
606
+ if len(n.Recv.List[0].Names) > 0 {
607
+ if ident := n.Recv.List[0].Names[0]; ident != nil && ident.Name != "_" {
608
+ if def := v.pkg.TypesInfo.Defs[ident]; def != nil {
609
+ if vr, ok := def.(*types.Var); ok {
610
+ v.currentReceiver = vr
611
+ // Add the receiver variable to the VariableUsage map
612
+ v.getOrCreateUsageInfo(v.currentReceiver)
613
+
614
+ // Check if receiver is used in method body
615
+ receiverUsed := false
616
+ if n.Body != nil {
617
+ if v.isInterfaceMethod(n) {
618
+ receiverUsed = true
619
+ } else {
620
+ receiverUsed = v.containsReceiverUsage(n.Body, vr)
512
621
  }
622
+ }
513
623
 
514
- // Update function data with receiver usage info
515
- if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
516
- if v.analysis.FunctionData[obj] == nil {
517
- v.analysis.FunctionData[obj] = &FunctionInfo{}
518
- }
519
- v.analysis.FunctionData[obj].ReceiverUsed = receiverUsed
520
- }
624
+ // Update function data with receiver usage info
625
+ if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
626
+ funcInfo := v.analysis.ensureFunctionData(obj)
627
+ funcInfo.ReceiverUsed = receiverUsed
521
628
  }
522
629
  }
523
630
  }
524
631
  }
525
632
  }
633
+ }
526
634
 
527
- // Store named return variables (sanitized for TypeScript)
528
- if n.Type != nil && n.Type.Results != nil {
529
- var namedReturns []string
530
- for _, field := range n.Type.Results.List {
531
- for _, name := range field.Names {
532
- namedReturns = append(namedReturns, sanitizeIdentifier(name.Name))
533
- }
534
- }
535
- if len(namedReturns) > 0 {
536
- if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
537
- if v.analysis.FunctionData[obj] == nil {
538
- v.analysis.FunctionData[obj] = &FunctionInfo{}
539
- }
540
- v.analysis.FunctionData[obj].NamedReturns = namedReturns
541
- }
635
+ // Store named return variables (sanitized for TypeScript)
636
+ if n.Type != nil && n.Type.Results != nil {
637
+ var namedReturns []string
638
+ for _, field := range n.Type.Results.List {
639
+ for _, name := range field.Names {
640
+ namedReturns = append(namedReturns, sanitizeIdentifier(name.Name))
542
641
  }
543
642
  }
544
-
545
- // Update visitor state for this function
546
- v.inAsyncFunction = isAsync
547
- v.currentFuncObj = v.pkg.TypesInfo.ObjectOf(n.Name)
548
- v.analysis.NodeData[n].InAsyncContext = isAsync // Ensure FuncDecl node itself is marked
549
-
550
- if n.Body != nil {
551
- // Check if the body contains any defer statements
552
- if v.containsDefer(n.Body) {
553
- if v.analysis.NodeData[n] == nil {
554
- v.analysis.NodeData[n] = &NodeInfo{}
555
- }
556
- v.analysis.NodeData[n].NeedsDefer = true
557
- }
558
-
559
- // Visit the body with updated state
560
- ast.Walk(v, n.Body)
561
- }
562
-
563
- // Restore states after visiting
564
- defer func() {
565
- v.currentFuncName = ""
566
- v.inAsyncFunction = originalInAsync
567
- v.currentReceiver = originalReceiver
568
- v.currentFuncObj = originalFuncObj
569
- v.currentFuncDecl = originalFuncDecl
570
- v.currentFuncLit = originalFuncLit
571
- }()
572
- return nil // Stop traversal here, ast.Walk handled the body
573
-
574
- case *ast.FuncLit:
575
- // Save original inAsyncFunction state to restore after visiting
576
- originalInAsync := v.inAsyncFunction
577
- originalFuncDecl := v.currentFuncDecl
578
- originalFuncLit := v.currentFuncLit
579
-
580
- // Set current function literal
581
- v.currentFuncDecl = nil
582
- v.currentFuncLit = n
583
-
584
- // Determine if this function literal is async based on its body
585
- isAsync := v.containsAsyncOperations(n.Body)
586
- if v.analysis.NodeData[n] == nil {
587
- v.analysis.NodeData[n] = &NodeInfo{}
588
- }
589
- v.analysis.NodeData[n].InAsyncContext = isAsync
590
-
591
- // Store named return variables for function literal
592
- if n.Type != nil && n.Type.Results != nil {
593
- var namedReturns []string
594
- for _, field := range n.Type.Results.List {
595
- for _, name := range field.Names {
596
- namedReturns = append(namedReturns, name.Name)
597
- }
598
- }
599
- if len(namedReturns) > 0 {
600
- v.analysis.FuncLitData[n] = &FunctionInfo{
601
- IsAsync: isAsync,
602
- NamedReturns: namedReturns,
603
- }
643
+ if len(namedReturns) > 0 {
644
+ if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
645
+ funcInfo := v.analysis.ensureFunctionData(obj)
646
+ funcInfo.NamedReturns = namedReturns
604
647
  }
605
648
  }
649
+ }
606
650
 
607
- v.inAsyncFunction = isAsync
651
+ // Update visitor state for this function
652
+ v.inAsyncFunction = isAsync
653
+ v.currentFuncObj = v.pkg.TypesInfo.ObjectOf(n.Name)
608
654
 
655
+ if n.Body != nil {
609
656
  // Check if the body contains any defer statements
610
- if n.Body != nil && v.containsDefer(n.Body) {
611
- if v.analysis.NodeData[n] == nil {
612
- v.analysis.NodeData[n] = &NodeInfo{}
613
- }
614
- v.analysis.NodeData[n].NeedsDefer = true
657
+ if v.containsDefer(n.Body) {
658
+ nodeInfo.NeedsDefer = true
615
659
  }
616
660
 
617
661
  // Visit the body with updated state
618
662
  ast.Walk(v, n.Body)
663
+ }
619
664
 
620
- // Restore inAsyncFunction state after visiting
665
+ // Restore states after visiting
666
+ defer func() {
667
+ v.currentFuncName = ""
621
668
  v.inAsyncFunction = originalInAsync
669
+ v.currentReceiver = originalReceiver
670
+ v.currentFuncObj = originalFuncObj
622
671
  v.currentFuncDecl = originalFuncDecl
623
672
  v.currentFuncLit = originalFuncLit
624
- return nil // Stop traversal here, ast.Walk handled the body
673
+ }()
674
+ return nil // Stop traversal here, ast.Walk handled the body
675
+ }
625
676
 
626
- case *ast.BlockStmt:
627
- if n == nil || len(n.List) == 0 {
628
- break
677
+ // visitFuncLit handles function literal analysis
678
+ func (v *analysisVisitor) visitFuncLit(n *ast.FuncLit) ast.Visitor {
679
+ // Save original inAsyncFunction state to restore after visiting
680
+ originalInAsync := v.inAsyncFunction
681
+ originalFuncDecl := v.currentFuncDecl
682
+ originalFuncLit := v.currentFuncLit
683
+
684
+ // Set current function literal
685
+ v.currentFuncDecl = nil
686
+ v.currentFuncLit = n
687
+
688
+ // Determine if this function literal is async based on its body
689
+ isAsync := v.containsAsyncOperations(n.Body)
690
+ nodeInfo := v.analysis.ensureNodeData(n)
691
+ nodeInfo.InAsyncContext = isAsync
692
+
693
+ // Store named return variables for function literal
694
+ if n.Type != nil && n.Type.Results != nil {
695
+ var namedReturns []string
696
+ for _, field := range n.Type.Results.List {
697
+ for _, name := range field.Names {
698
+ namedReturns = append(namedReturns, name.Name)
699
+ }
629
700
  }
630
-
631
- // Initialize NodeData for this block
632
- if v.analysis.NodeData[n] == nil {
633
- v.analysis.NodeData[n] = &NodeInfo{}
701
+ if len(namedReturns) > 0 {
702
+ v.analysis.FuncLitData[n] = &FunctionInfo{
703
+ IsAsync: isAsync,
704
+ NamedReturns: namedReturns,
705
+ }
634
706
  }
707
+ }
635
708
 
636
- // Check for defer statements in this block
637
- if v.containsDefer(n) {
638
- v.analysis.NodeData[n].NeedsDefer = true
639
- }
709
+ v.inAsyncFunction = isAsync
640
710
 
641
- // Store async state for this block
642
- v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
711
+ // Check if the body contains any defer statements
712
+ if n.Body != nil && v.containsDefer(n.Body) {
713
+ nodeInfo.NeedsDefer = true
714
+ }
643
715
 
644
- return v
716
+ // Visit the body with updated state
717
+ ast.Walk(v, n.Body)
645
718
 
646
- case *ast.UnaryExpr:
647
- // We handle address-of (&) within AssignStmt where it's actually used.
648
- // Standalone &x doesn't directly assign, but its usage in assignments
649
- // or function calls determines varRefing. Assignments are handled below.
650
- // Function calls like foo(&x) would require different tracking if needed.
651
- // TODO: for now, we focus on assignments
719
+ // Restore inAsyncFunction state after visiting
720
+ v.inAsyncFunction = originalInAsync
721
+ v.currentFuncDecl = originalFuncDecl
722
+ v.currentFuncLit = originalFuncLit
723
+ return nil // Stop traversal here, ast.Walk handled the body
724
+ }
725
+
726
+ // visitBlockStmt handles block statement analysis
727
+ func (v *analysisVisitor) visitBlockStmt(n *ast.BlockStmt) ast.Visitor {
728
+ if n == nil || len(n.List) == 0 {
652
729
  return v
730
+ }
653
731
 
654
- case *ast.CallExpr:
655
- // Check if this is a function call that might be async
656
- if funcIdent, ok := n.Fun.(*ast.Ident); ok {
657
- // Get the object for this function call
658
- if obj := v.pkg.TypesInfo.Uses[funcIdent]; obj != nil && v.analysis.IsAsyncFunc(obj) {
659
- // We're calling an async function, so mark current function as async if we're in one
660
- if v.currentFuncObj != nil {
661
- v.analysis.FunctionData[v.currentFuncObj] = &FunctionInfo{
662
- IsAsync: true,
663
- NamedReturns: v.getNamedReturns(v.currentFuncDecl),
664
- }
665
- v.inAsyncFunction = true // Update visitor state
666
- // Mark the FuncDecl node itself if possible (might need to store the node too)
667
- for nodeAst := range v.analysis.NodeData { // Find the node to update
668
- if fd, ok := nodeAst.(*ast.FuncDecl); ok && v.pkg.TypesInfo.ObjectOf(fd.Name) == v.currentFuncObj {
669
- if v.analysis.NodeData[nodeAst] == nil {
670
- v.analysis.NodeData[nodeAst] = &NodeInfo{}
671
- }
672
- v.analysis.NodeData[nodeAst].InAsyncContext = true
673
- }
732
+ // Initialize NodeData for this block
733
+ nodeInfo := v.analysis.ensureNodeData(n)
734
+
735
+ // Check for defer statements in this block
736
+ if v.containsDefer(n) {
737
+ nodeInfo.NeedsDefer = true
738
+ }
739
+
740
+ return v
741
+ }
742
+
743
+ // visitCallExpr handles call expression analysis
744
+ func (v *analysisVisitor) visitCallExpr(n *ast.CallExpr) ast.Visitor {
745
+ // Check if this is a function call that might be async
746
+ if funcIdent, ok := n.Fun.(*ast.Ident); ok {
747
+ // Get the object for this function call
748
+ if obj := v.pkg.TypesInfo.Uses[funcIdent]; obj != nil && v.analysis.IsAsyncFunc(obj) {
749
+ // We're calling an async function, so mark current function as async if we're in one
750
+ if v.currentFuncObj != nil {
751
+ funcInfo := v.analysis.ensureFunctionData(v.currentFuncObj)
752
+ funcInfo.IsAsync = true
753
+ funcInfo.NamedReturns = v.getNamedReturns(v.currentFuncDecl)
754
+ v.inAsyncFunction = true // Update visitor state
755
+ // Mark the FuncDecl node itself if possible
756
+ for nodeAst := range v.analysis.NodeData {
757
+ if fd, ok := nodeAst.(*ast.FuncDecl); ok && v.pkg.TypesInfo.ObjectOf(fd.Name) == v.currentFuncObj {
758
+ nodeInfo := v.analysis.ensureNodeData(nodeAst)
759
+ nodeInfo.InAsyncContext = true
674
760
  }
675
761
  }
676
762
  }
677
763
  }
764
+ }
678
765
 
679
- // Check for reflect function calls that operate on functions
680
- v.checkReflectUsage(n)
766
+ // Check for reflect function calls that operate on functions
767
+ v.checkReflectUsage(n)
681
768
 
682
- // Store async state for this call expression
683
- if v.analysis.NodeData[n] == nil {
684
- v.analysis.NodeData[n] = &NodeInfo{}
769
+ return v
770
+ }
771
+
772
+ // visitSelectorExpr handles selector expression analysis
773
+ func (v *analysisVisitor) visitSelectorExpr(n *ast.SelectorExpr) ast.Visitor {
774
+ // Check if this is a method value (method being used as a value, not called immediately)
775
+ if selection := v.pkg.TypesInfo.Selections[n]; selection != nil {
776
+ if selection.Kind() == types.MethodVal {
777
+ // This is a method value - mark it for binding during code generation
778
+ nodeInfo := v.analysis.ensureNodeData(n)
779
+ nodeInfo.IsMethodValue = true
685
780
  }
686
- v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
781
+ }
782
+ return v
783
+ }
687
784
 
688
- return v
785
+ // visitAssignStmt handles assignment statement analysis
786
+ func (v *analysisVisitor) visitAssignStmt(n *ast.AssignStmt) ast.Visitor {
787
+ // Handle variable assignment tracking and generate shadowing information
788
+ shadowingInfo := v.detectVariableShadowing(n)
689
789
 
690
- case *ast.SelectorExpr:
691
- // Initialize NodeData for this selector expression
692
- if v.analysis.NodeData[n] == nil {
693
- v.analysis.NodeData[n] = &NodeInfo{}
694
- }
695
- v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
790
+ // Store shadowing information if needed for code generation
791
+ if shadowingInfo != nil {
792
+ nodeInfo := v.analysis.ensureNodeData(n)
793
+ nodeInfo.ShadowingInfo = shadowingInfo
794
+ }
696
795
 
697
- // Check if this is a method value (method being used as a value, not called immediately)
698
- if selection := v.pkg.TypesInfo.Selections[n]; selection != nil {
699
- if selection.Kind() == types.MethodVal {
700
- // This is a method value - mark it for binding during code generation
701
- v.analysis.NodeData[n].IsMethodValue = true
702
- }
796
+ // Track assignment relationships for pointer analysis
797
+ for i, lhsExpr := range n.Lhs {
798
+ if i < len(n.Rhs) {
799
+ v.analyzeAssignment(lhsExpr, n.Rhs[i])
703
800
  }
704
- return v // Continue traversal
801
+ }
705
802
 
706
- case *ast.AssignStmt:
707
- // Detect variable shadowing in any := assignment
708
- if n.Tok == token.DEFINE {
709
- shadowingInfo := v.detectVariableShadowing(n)
710
- if shadowingInfo != nil {
711
- // Store shadowing info on the assignment statement itself
712
- if v.analysis.NodeData[n] == nil {
713
- v.analysis.NodeData[n] = &NodeInfo{}
803
+ // Track interface implementations for assignments to interface variables
804
+ v.trackInterfaceAssignments(n)
805
+
806
+ // Track function assignments (function literals assigned to variables)
807
+ if len(n.Lhs) == 1 && len(n.Rhs) == 1 {
808
+ if lhsIdent, ok := n.Lhs[0].(*ast.Ident); ok {
809
+ if rhsFuncLit, ok := n.Rhs[0].(*ast.FuncLit); ok {
810
+ // Get the object for the LHS variable
811
+ if obj := v.pkg.TypesInfo.ObjectOf(lhsIdent); obj != nil {
812
+ v.analysis.FunctionAssignments[obj] = rhsFuncLit
714
813
  }
715
- v.analysis.NodeData[n].ShadowingInfo = shadowingInfo
716
814
  }
717
815
  }
816
+ }
718
817
 
719
- // Continue with the existing assignment analysis logic
720
- for i, currentLHSExpr := range n.Lhs {
721
- if i >= len(n.Rhs) {
722
- break // Should not happen in valid Go
723
- }
724
- currentRHSExpr := n.Rhs[i]
725
-
726
- // --- Analyze RHS to determine assignment type and source object (if any) ---
727
- rhsAssignmentType := DirectAssignment
728
- var rhsSourceObj types.Object // The variable object on the RHS (e.g., 'y' in x = y or x = &y)
729
-
730
- if unaryExpr, ok := currentRHSExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
731
- // RHS is &some_expr
732
- rhsAssignmentType = AddressOfAssignment
733
- if rhsIdent, ok := unaryExpr.X.(*ast.Ident); ok {
734
- // RHS is &variable
735
- rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
736
- }
737
- // If RHS is &structLit{} or &array[0], rhsSourceObj remains nil.
738
- // _, ok := unaryExpr.X.(*ast.CompositeLit); ok
739
- } else if rhsIdent, ok := currentRHSExpr.(*ast.Ident); ok {
740
- // RHS is variable
741
- rhsAssignmentType = DirectAssignment
742
- rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
743
- }
744
- // If RHS is a literal, function call, etc., rhsSourceObj remains nil.
818
+ return v
819
+ }
745
820
 
746
- // --- Determine the LHS object (if it's a simple variable or a known field) ---
747
- var lhsTrackedObj types.Object // The object on the LHS we might record info *for* (e.g. its sources)
821
+ // visitReturnStmt handles return statement analysis
822
+ func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
823
+ nodeInfo := v.analysis.ensureNodeData(n)
748
824
 
749
- if lhsIdent, ok := currentLHSExpr.(*ast.Ident); ok {
750
- if lhsIdent.Name == "_" {
751
- continue // Skip blank identifier assignments
752
- }
753
- lhsTrackedObj = v.pkg.TypesInfo.ObjectOf(lhsIdent)
825
+ // Record the enclosing function/literal for this return statement
826
+ if v.currentFuncDecl != nil {
827
+ nodeInfo.EnclosingFuncDecl = v.currentFuncDecl
828
+ } else if v.currentFuncLit != nil {
829
+ nodeInfo.EnclosingFuncLit = v.currentFuncLit
830
+ }
754
831
 
755
- // Check if RHS is a function literal and track the assignment
756
- if funcLit, ok := currentRHSExpr.(*ast.FuncLit); ok {
757
- v.analysis.FunctionAssignments[lhsTrackedObj] = funcLit
758
- }
759
- } else if selExpr, ok := currentLHSExpr.(*ast.SelectorExpr); ok {
760
- // LHS is struct.field or package.Var
761
- if selection := v.pkg.TypesInfo.Selections[selExpr]; selection != nil {
762
- lhsTrackedObj = selection.Obj() // This is the field or selected var object
763
- }
764
- } /* else if _, ok := currentLHSExpr.(*ast.StarExpr); ok {
765
- // LHS is *pointer.
766
- // We don't try to get a types.Object for the dereferenced entity itself to store in VariableUsage.
767
- // lhsTrackedObj remains nil. The effect on rhsSourceObj (if its address is taken) is handled below.
768
- } */
769
- // For other complex LHS (e.g., map_expr[key_expr]), lhsTrackedObj remains nil.
770
-
771
- // --- Record Usage Information ---
772
-
773
- // 1. If LHS is a trackable variable/field, record what's assigned to it (its sources).
774
- // We only want to create VariableUsage entries for actual variables/fields.
775
- if _, isVar := lhsTrackedObj.(*types.Var); isVar {
776
- lhsUsageInfo := v.getOrCreateUsageInfo(lhsTrackedObj)
777
- if rhsSourceObj != nil {
778
- // Case: var1 = var2 OR var1 = &var2 OR field1 = var2 OR field1 = &var2
779
- lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
780
- Object: rhsSourceObj,
781
- Type: rhsAssignmentType,
782
- })
783
- } else if rhsAssignmentType == AddressOfAssignment {
784
- // Case: var1 = &non_ident_expr (e.g., &T{}) OR field1 = &non_ident_expr
785
- // lhsTrackedObj is assigned an address, but not of a named variable.
786
- lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
787
- Object: nil, // No specific source variable object
788
- Type: rhsAssignmentType,
789
- })
832
+ // Check if it's a bare return
833
+ if len(n.Results) == 0 {
834
+ if v.currentFuncDecl != nil {
835
+ // Check if the enclosing function declaration has named returns
836
+ if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
837
+ if _, ok := v.analysis.FunctionData[obj]; ok {
838
+ nodeInfo.IsBareReturn = true
790
839
  }
791
- // If rhsSourceObj is nil and rhsAssignmentType is DirectAssignment (e.g. var1 = 10),
792
- // no source object to record for LHS sources.
793
- }
794
-
795
- // 2. If RHS involved a source variable (rhsSourceObj is not nil),
796
- // record that this source variable was used (its destinations).
797
- // This is CRITICAL for varRefing analysis (e.g., if &rhsSourceObj was assigned).
798
- if rhsSourceObj != nil {
799
- sourceUsageInfo := v.getOrCreateUsageInfo(rhsSourceObj)
800
- // The 'Object' in DestinationInfo is what/where rhsSourceObj (or its address) was assigned TO.
801
- // This can be lhsTrackedObj (if LHS was an ident or field).
802
- // If LHS was complex (e.g., *ptr, map[k]), lhsTrackedObj might be nil for that DestinationInfo.Object.
803
- // Even if lhsTrackedObj is nil for the DestinationInfo.Object, if rhsAssignmentType is AddressOfAssignment,
804
- // it's important to record that rhsSourceObj's address was taken.
805
- sourceUsageInfo.Destinations = append(sourceUsageInfo.Destinations, AssignmentInfo{
806
- Object: lhsTrackedObj, // This can be nil if LHS is complex (*p, map[k])
807
- Type: rhsAssignmentType,
808
- })
809
840
  }
810
- }
811
- return v // Continue traversal
812
-
813
- case *ast.ReturnStmt:
814
- // Initialize NodeData for return statement
815
- if v.analysis.NodeData[n] == nil {
816
- v.analysis.NodeData[n] = &NodeInfo{}
817
- }
818
-
819
- // Record the enclosing function/literal for this return statement
820
- if v.currentFuncDecl != nil {
821
- v.analysis.NodeData[n].EnclosingFuncDecl = v.currentFuncDecl
822
841
  } else if v.currentFuncLit != nil {
823
- v.analysis.NodeData[n].EnclosingFuncLit = v.currentFuncLit
824
- }
825
-
826
- // Check if it's a bare return
827
- if len(n.Results) == 0 {
828
- if v.currentFuncDecl != nil {
829
- // Check if the enclosing function declaration has named returns
830
- if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
831
- if _, ok := v.analysis.FunctionData[obj]; ok {
832
- if v.analysis.NodeData[n] == nil {
833
- v.analysis.NodeData[n] = &NodeInfo{}
834
- }
835
- v.analysis.NodeData[n].IsBareReturn = true
836
- }
837
- }
838
- } else if v.currentFuncLit != nil {
839
- // Check if the enclosing function literal has named returns
840
- if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
841
- if v.analysis.NodeData[n] == nil {
842
- v.analysis.NodeData[n] = &NodeInfo{}
843
- }
844
- v.analysis.NodeData[n].IsBareReturn = true
845
- }
842
+ // Check if the enclosing function literal has named returns
843
+ if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
844
+ nodeInfo.IsBareReturn = true
846
845
  }
847
846
  }
848
- return v // Continue traversal
847
+ }
848
+ return v
849
+ }
849
850
 
850
- case *ast.DeclStmt:
851
- // Handle declarations inside functions (const, var, type declarations within function bodies)
852
- // These should not have export modifiers in TypeScript
853
- if genDecl, ok := n.Decl.(*ast.GenDecl); ok {
854
- // Check if we're inside a function (either FuncDecl or FuncLit)
855
- isInsideFunction := v.currentFuncDecl != nil || v.currentFuncLit != nil
856
-
857
- if isInsideFunction {
858
- // Mark all specs in this declaration as being inside a function
859
- for _, spec := range genDecl.Specs {
860
- if v.analysis.NodeData[spec] == nil {
861
- v.analysis.NodeData[spec] = &NodeInfo{}
862
- }
863
- v.analysis.NodeData[spec].IsInsideFunction = true
864
- }
851
+ // visitDeclStmt handles declaration statement analysis
852
+ func (v *analysisVisitor) visitDeclStmt(n *ast.DeclStmt) ast.Visitor {
853
+ // Handle declarations inside functions (const, var, type declarations within function bodies)
854
+ // These should not have export modifiers in TypeScript
855
+ if genDecl, ok := n.Decl.(*ast.GenDecl); ok {
856
+ // Check if we're inside a function (either FuncDecl or FuncLit)
857
+ isInsideFunction := v.currentFuncDecl != nil || v.currentFuncLit != nil
858
+
859
+ if isInsideFunction {
860
+ // Mark all specs in this declaration as being inside a function
861
+ for _, spec := range genDecl.Specs {
862
+ nodeInfo := v.analysis.ensureNodeData(spec)
863
+ nodeInfo.IsInsideFunction = true
865
864
  }
866
865
  }
867
- return v // Continue traversal
866
+ }
867
+ return v
868
+ }
868
869
 
869
- case *ast.IfStmt:
870
- // Detect variable shadowing in if statement initializations
871
- if n.Init != nil {
872
- if assignStmt, ok := n.Init.(*ast.AssignStmt); ok && assignStmt.Tok == token.DEFINE {
873
- shadowingInfo := v.detectVariableShadowing(assignStmt)
874
- if shadowingInfo != nil {
875
- // Initialize NodeData for this if statement
876
- if v.analysis.NodeData[n] == nil {
877
- v.analysis.NodeData[n] = &NodeInfo{}
878
- }
879
- v.analysis.NodeData[n].ShadowingInfo = shadowingInfo
880
- }
870
+ // visitIfStmt handles if statement analysis
871
+ func (v *analysisVisitor) visitIfStmt(n *ast.IfStmt) ast.Visitor {
872
+ // Detect variable shadowing in if statement initializations
873
+ if n.Init != nil {
874
+ if assignStmt, ok := n.Init.(*ast.AssignStmt); ok && assignStmt.Tok == token.DEFINE {
875
+ shadowingInfo := v.detectVariableShadowing(assignStmt)
876
+ if shadowingInfo != nil {
877
+ nodeInfo := v.analysis.ensureNodeData(n)
878
+ nodeInfo.ShadowingInfo = shadowingInfo
881
879
  }
882
880
  }
883
- return v // Continue traversal
884
881
  }
882
+ return v
883
+ }
885
884
 
886
- // For all other nodes, continue traversal
885
+ // visitTypeAssertExpr handles type assertion expression analysis
886
+ func (v *analysisVisitor) visitTypeAssertExpr(n *ast.TypeAssertExpr) ast.Visitor {
887
+ // Track interface implementations when we see type assertions
888
+ v.trackTypeAssertion(n)
887
889
  return v
888
890
  }
889
891
 
@@ -933,11 +935,11 @@ func (v *analysisVisitor) containsAsyncOperations(node ast.Node) bool {
933
935
 
934
936
  // Check if the type is from an imported package
935
937
  if typePkg := namedType.Obj().Pkg(); typePkg != nil && typePkg != v.pkg.Types {
936
- // Use the actual package name from the type information
937
- pkgName := typePkg.Name()
938
+ // Use the full package path from the type information (not just the package name)
939
+ pkgPath := typePkg.Path()
938
940
 
939
941
  // Check if this method is async based on metadata
940
- if v.analysis.IsMethodAsync(pkgName, typeName, methodName) {
942
+ if v.analysis.IsMethodAsync(pkgPath, typeName, methodName) {
941
943
  hasAsync = true
942
944
  return false
943
945
  }
@@ -1043,99 +1045,113 @@ func (v *analysisVisitor) couldImplementInterfaceMethod(methodName string, recei
1043
1045
  return false
1044
1046
  }
1045
1047
 
1046
- // AnalyzeFile analyzes a Go source file AST and populates the Analysis struct with information
1047
- // that will be used during code generation to properly handle pointers, variables that need varRefing, etc.
1048
- func AnalyzeFile(file *ast.File, pkg *packages.Package, analysis *Analysis, cmap ast.CommentMap) {
1049
- // Store the comment map in the analysis object
1050
- analysis.Cmap = cmap
1048
+ // AnalyzePackageFiles analyzes all Go source files in a package and populates the Analysis struct
1049
+ // with information that will be used during code generation to properly handle pointers,
1050
+ // variables that need varRefing, receiver usage, etc. This replaces the old file-by-file analysis.
1051
+ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages.Package) *Analysis {
1052
+ analysis := NewAnalysis(allPackages)
1051
1053
 
1052
1054
  // Load package metadata for async function detection
1053
1055
  analysis.LoadPackageMetadata()
1054
1056
 
1055
- // Process imports from the file
1056
- for _, imp := range file.Imports {
1057
- path := ""
1058
- if imp.Path != nil {
1059
- path = imp.Path.Value
1060
- // Remove quotes from the import path string
1061
- path = path[1 : len(path)-1]
1057
+ // Process imports from all files in the package
1058
+ for _, file := range pkg.Syntax {
1059
+ // Create comment map for each file and store it (we'll merge them if needed)
1060
+ cmap := ast.NewCommentMap(pkg.Fset, file, file.Comments)
1061
+ if len(analysis.Cmap) == 0 {
1062
+ analysis.Cmap = cmap
1063
+ } else {
1064
+ // Merge comment maps from multiple files
1065
+ for node, comments := range cmap {
1066
+ analysis.Cmap[node] = append(analysis.Cmap[node], comments...)
1067
+ }
1062
1068
  }
1063
1069
 
1064
- // Store the import in the analysis
1065
- if path != "" {
1066
- name := ""
1067
- if imp.Name != nil {
1068
- name = imp.Name.Name
1070
+ // Process imports from this file
1071
+ for _, imp := range file.Imports {
1072
+ path := ""
1073
+ if imp.Path != nil {
1074
+ path = imp.Path.Value
1075
+ // Remove quotes from the import path string
1076
+ path = path[1 : len(path)-1]
1069
1077
  }
1070
1078
 
1071
- fileImp := &fileImport{
1072
- importPath: path,
1073
- importVars: make(map[string]struct{}),
1074
- }
1079
+ // Store the import in the analysis
1080
+ if path != "" {
1081
+ name := ""
1082
+ if imp.Name != nil {
1083
+ name = imp.Name.Name
1084
+ }
1085
+
1086
+ fileImp := &fileImport{
1087
+ importPath: path,
1088
+ importVars: make(map[string]struct{}),
1089
+ }
1075
1090
 
1076
- // Use the import name or the actual package name as the key
1077
- var key string
1078
- if name != "" {
1079
- // Explicit alias provided
1080
- key = name
1081
- } else {
1082
- // No explicit alias, use the actual package name from type information
1083
- // This handles cases where package name differs from the last path segment
1084
- if actualName, err := getActualPackageName(path, pkg.Imports); err == nil {
1085
- key = actualName
1091
+ // Use the import name or the actual package name as the key
1092
+ var key string
1093
+ if name != "" {
1094
+ // Explicit alias provided
1095
+ key = name
1086
1096
  } else {
1087
- // Fallback to last segment of path if package not found in type information
1088
- pts := strings.Split(path, "/")
1089
- key = pts[len(pts)-1]
1097
+ // No explicit alias, use the actual package name from type information
1098
+ // This handles cases where package name differs from the last path segment
1099
+ if actualName, err := getActualPackageName(path, pkg.Imports); err == nil {
1100
+ key = actualName
1101
+ } else {
1102
+ // Fallback to last segment of path if package not found in type information
1103
+ pts := strings.Split(path, "/")
1104
+ key = pts[len(pts)-1]
1105
+ }
1090
1106
  }
1091
- }
1092
1107
 
1093
- analysis.Imports[key] = fileImp
1108
+ analysis.Imports[key] = fileImp
1109
+ }
1094
1110
  }
1095
1111
  }
1096
1112
 
1097
- // Create an analysis visitor to traverse the AST
1113
+ // Create visitor for the entire package
1098
1114
  visitor := &analysisVisitor{
1099
- analysis: analysis,
1100
- pkg: pkg,
1101
- inAsyncFunction: false,
1102
- currentReceiver: nil, // Initialize currentReceiver
1115
+ analysis: analysis,
1116
+ pkg: pkg,
1103
1117
  }
1104
1118
 
1105
- // Walk the AST with our visitor
1106
- ast.Walk(visitor, file)
1119
+ // First pass: analyze all declarations and statements across all files
1120
+ for _, file := range pkg.Syntax {
1121
+ ast.Walk(visitor, file)
1122
+ }
1107
1123
 
1108
1124
  // Post-processing: Find all CallExpr nodes and unmark their Fun SelectorExpr as method values
1109
1125
  // This distinguishes between method calls (obj.Method()) and method values (obj.Method)
1110
- ast.Inspect(file, func(n ast.Node) bool {
1111
- if callExpr, ok := n.(*ast.CallExpr); ok {
1112
- if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
1113
- // This SelectorExpr is the function being called, so it's NOT a method value
1114
- if nodeInfo := analysis.NodeData[selExpr]; nodeInfo != nil {
1115
- nodeInfo.IsMethodValue = false
1126
+ for _, file := range pkg.Syntax {
1127
+ ast.Inspect(file, func(n ast.Node) bool {
1128
+ if callExpr, ok := n.(*ast.CallExpr); ok {
1129
+ if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
1130
+ // This SelectorExpr is the function being called, so it's NOT a method value
1131
+ if nodeInfo := analysis.NodeData[selExpr]; nodeInfo != nil {
1132
+ nodeInfo.IsMethodValue = false
1133
+ }
1116
1134
  }
1117
1135
  }
1118
- }
1119
- return true
1120
- })
1121
- }
1136
+ return true
1137
+ })
1138
+ }
1122
1139
 
1123
- // getNamedReturns retrieves the named returns for a function
1124
- func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
1125
- var namedReturns []string
1126
- if funcDecl.Type != nil && funcDecl.Type.Results != nil {
1127
- for _, field := range funcDecl.Type.Results.List {
1128
- for _, name := range field.Names {
1129
- namedReturns = append(namedReturns, name.Name)
1130
- }
1131
- }
1140
+ // Second pass: analyze interface implementations now that all function async status is determined
1141
+ interfaceVisitor := &interfaceImplementationVisitor{
1142
+ analysis: analysis,
1143
+ pkg: pkg,
1132
1144
  }
1133
- return namedReturns
1145
+ for _, file := range pkg.Syntax {
1146
+ ast.Walk(interfaceVisitor, file)
1147
+ }
1148
+
1149
+ return analysis
1134
1150
  }
1135
1151
 
1136
- // AnalyzePackage performs package-level analysis to collect function definitions
1152
+ // AnalyzePackageImports performs package-level analysis to collect function definitions
1137
1153
  // and calls across all files in the package for auto-import generation
1138
- func AnalyzePackage(pkg *packages.Package) *PackageAnalysis {
1154
+ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1139
1155
  analysis := NewPackageAnalysis()
1140
1156
 
1141
1157
  // First pass: collect all function definitions per file
@@ -1299,92 +1315,202 @@ func AnalyzePackage(pkg *packages.Package) *PackageAnalysis {
1299
1315
  return analysis
1300
1316
  }
1301
1317
 
1302
- // LoadPackageMetadata loads metadata from gs packages to determine which functions are async
1318
+ // LoadPackageMetadata loads metadata from gs packages using embedded JSON files
1303
1319
  func (a *Analysis) LoadPackageMetadata() {
1304
- // List of gs packages that have metadata
1305
- metadataPackages := []string{
1306
- "github.com/aperturerobotics/goscript/gs/sync",
1307
- "github.com/aperturerobotics/goscript/gs/unicode",
1308
- }
1320
+ // Discover all packages in the embedded gs/ directory
1321
+ packagePaths := a.discoverEmbeddedGsPackages()
1322
+
1323
+ for _, pkgPath := range packagePaths {
1324
+ metaFilePath := filepath.Join("gs", pkgPath, "meta.json")
1325
+
1326
+ // Try to read the meta.json file from embedded filesystem
1327
+ // We need access to the embedded FS, which should be imported from the parent package
1328
+ if metadata := a.loadGsMetadata(metaFilePath); metadata != nil {
1329
+ // Store async method information
1330
+ for methodKey, isAsync := range metadata.AsyncMethods {
1331
+ // Convert "Type.Method" format to our internal key format
1332
+ parts := strings.Split(methodKey, ".")
1333
+ if len(parts) == 2 {
1334
+ typeName := parts[0]
1335
+ methodName := parts[1]
1336
+ // The key format is "pkgName.TypeNameMethodNameInfo"
1337
+ key := PackageMetadataKey{
1338
+ PackagePath: pkgPath,
1339
+ TypeName: typeName,
1340
+ MethodName: methodName,
1341
+ }
1309
1342
 
1310
- for _, pkgPath := range metadataPackages {
1311
- cfg := &packages.Config{
1312
- Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax,
1343
+ // Store the async value directly in PackageMetadata
1344
+ a.PackageMetadata[key] = MethodMetadata{
1345
+ IsAsync: isAsync,
1346
+ }
1347
+ }
1348
+ }
1313
1349
  }
1350
+ }
1351
+ }
1314
1352
 
1315
- pkgs, err := packages.Load(cfg, pkgPath)
1316
- if err != nil || len(pkgs) == 0 {
1317
- continue // Skip if package can't be loaded
1318
- }
1353
+ // discoverEmbeddedGsPackages finds all packages in the embedded gs/ directory
1354
+ func (a *Analysis) discoverEmbeddedGsPackages() []string {
1355
+ var packageList []string
1319
1356
 
1320
- pkg := pkgs[0]
1321
- if pkg.Types == nil {
1322
- continue
1323
- }
1357
+ // Read the gs/ directory from the embedded filesystem
1358
+ entries, err := goscript.GsOverrides.ReadDir("gs")
1359
+ if err != nil {
1360
+ // If we can't read the gs/ directory, return empty list
1361
+ return packageList
1362
+ }
1324
1363
 
1325
- // Extract the package name (e.g., "sync" from "github.com/aperturerobotics/goscript/gs/sync")
1326
- parts := strings.Split(pkgPath, "/")
1327
- pkgName := parts[len(parts)-1]
1364
+ // Iterate through all entries in gs/
1365
+ for _, entry := range entries {
1366
+ if entry.IsDir() {
1367
+ packageName := entry.Name()
1328
1368
 
1329
- // Look for metadata variables in the package scope
1330
- scope := pkg.Types.Scope()
1331
- for _, name := range scope.Names() {
1332
- obj := scope.Lookup(name)
1333
- if obj == nil {
1369
+ // Skip special directories like github.com
1370
+ if strings.Contains(packageName, ".") {
1334
1371
  continue
1335
1372
  }
1336
1373
 
1337
- // Check if this is a metadata variable (ends with "Info")
1338
- if strings.HasSuffix(name, "Info") {
1339
- if varObj, ok := obj.(*types.Var); ok {
1340
- // Store the metadata with a key like "sync.MutexLock"
1341
- methodName := strings.TrimSuffix(name, "Info")
1342
- key := pkgName + "." + methodName
1343
- a.PackageMetadata[key] = varObj
1344
- }
1345
- }
1374
+ packageList = append(packageList, packageName)
1346
1375
  }
1347
1376
  }
1377
+
1378
+ return packageList
1379
+ }
1380
+
1381
+ // loadGsMetadata loads metadata from a meta.json file in the embedded filesystem
1382
+ func (a *Analysis) loadGsMetadata(metaFilePath string) *GsMetadata {
1383
+ // Read the meta.json file from the embedded filesystem
1384
+ content, err := goscript.GsOverrides.ReadFile(metaFilePath)
1385
+ if err != nil {
1386
+ return nil // No metadata file found
1387
+ }
1388
+
1389
+ var metadata GsMetadata
1390
+ if err := json.Unmarshal(content, &metadata); err != nil {
1391
+ return nil // Invalid JSON
1392
+ }
1393
+
1394
+ return &metadata
1348
1395
  }
1349
1396
 
1350
1397
  // IsMethodAsync checks if a method call is async based on package metadata
1351
- func (a *Analysis) IsMethodAsync(pkgName, typeName, methodName string) bool {
1352
- // The metadata keys are stored as "sync.MutexLock", "sync.WaitGroupWait", etc.
1353
- // We need to match "sync.Mutex.Lock" -> "sync.MutexLock"
1354
- key := pkgName + "." + typeName + methodName
1355
-
1356
- if metaObj, exists := a.PackageMetadata[key]; exists {
1357
- if varObj, ok := metaObj.(*types.Var); ok {
1358
- // Try to get the actual value of the variable
1359
- // For now, we'll use the variable name to determine if it's async
1360
- // The variable names follow the pattern: MutexLockInfo, WaitGroupWaitInfo, etc.
1361
- // We can check if the corresponding metadata indicates IsAsync: true
1362
- varName := varObj.Name()
1363
-
1364
- // Based on our metadata definitions, these should be async:
1365
- asyncMethods := map[string]bool{
1366
- "MutexLockInfo": true,
1367
- "RWMutexLockInfo": true,
1368
- "RWMutexRLockInfo": true,
1369
- "WaitGroupWaitInfo": true,
1370
- "OnceDoInfo": true,
1371
- "CondWaitInfo": true,
1372
- "MapDeleteInfo": true,
1373
- "MapLoadInfo": true,
1374
- "MapLoadAndDeleteInfo": true,
1375
- "MapLoadOrStoreInfo": true,
1376
- "MapRangeInfo": true,
1377
- "MapStoreInfo": true,
1378
- }
1398
+ func (a *Analysis) IsMethodAsync(pkgPath, typeName, methodName string) bool {
1399
+ // First, check existing metadata for internal packages
1400
+ // The metadata keys are stored as "pkgName.TypeNameMethodNameInfo"
1401
+ // e.g., "sync.MutexLockInfo", "sync.WaitGroupWaitInfo", etc.
1402
+ key := PackageMetadataKey{
1403
+ PackagePath: pkgPath,
1404
+ TypeName: typeName,
1405
+ MethodName: methodName,
1406
+ }
1407
+
1408
+ if metadata, exists := a.PackageMetadata[key]; exists {
1409
+ return metadata.IsAsync
1410
+ }
1411
+
1412
+ // If no metadata found, check if we can analyze the method from dependency packages
1413
+ return a.analyzeExternalMethodAsync(pkgPath, typeName, methodName)
1414
+ }
1415
+
1416
+ // analyzeExternalMethodAsync analyzes external package methods to determine if they're async
1417
+ // using the same logic we use for internal methods
1418
+ func (a *Analysis) analyzeExternalMethodAsync(pkgPath, typeName, methodName string) bool {
1419
+ // Look up the package in our loaded packages
1420
+ pkg, exists := a.AllPackages[pkgPath]
1421
+ if !exists {
1422
+ return false
1423
+ }
1424
+
1425
+ // Find the method definition in the package
1426
+ for _, syntax := range pkg.Syntax {
1427
+ for _, decl := range syntax.Decls {
1428
+ if funcDecl, ok := decl.(*ast.FuncDecl); ok {
1429
+ // Check if this is a method with the right name and receiver type
1430
+ if funcDecl.Name.Name == methodName && funcDecl.Recv != nil {
1431
+ // Check the receiver type
1432
+ if len(funcDecl.Recv.List) > 0 {
1433
+ receiverType := funcDecl.Recv.List[0].Type
1434
+
1435
+ // Handle pointer receivers
1436
+ if starExpr, ok := receiverType.(*ast.StarExpr); ok {
1437
+ receiverType = starExpr.X
1438
+ }
1379
1439
 
1380
- isAsync := asyncMethods[varName]
1381
- return isAsync
1440
+ if ident, ok := receiverType.(*ast.Ident); ok {
1441
+ if ident.Name == typeName {
1442
+ // Found the method! Now check if it's async
1443
+ if funcDecl.Body != nil {
1444
+ return a.containsAsyncOperations(funcDecl.Body, pkg)
1445
+ }
1446
+ }
1447
+ }
1448
+ }
1449
+ }
1450
+ }
1382
1451
  }
1383
1452
  }
1384
1453
 
1385
1454
  return false
1386
1455
  }
1387
1456
 
1457
+ // containsAsyncOperations checks if a node contains any async operations for a specific package
1458
+ func (a *Analysis) containsAsyncOperations(node ast.Node, pkg *packages.Package) bool {
1459
+ var hasAsync bool
1460
+
1461
+ ast.Inspect(node, func(n ast.Node) bool {
1462
+ if n == nil {
1463
+ return false
1464
+ }
1465
+
1466
+ switch s := n.(type) {
1467
+ case *ast.SendStmt:
1468
+ // Channel send operation (ch <- value)
1469
+ hasAsync = true
1470
+ return false
1471
+
1472
+ case *ast.UnaryExpr:
1473
+ // Channel receive operation (<-ch)
1474
+ if s.Op == token.ARROW {
1475
+ hasAsync = true
1476
+ return false
1477
+ }
1478
+
1479
+ case *ast.CallExpr:
1480
+ // Check for select statements and other async operations
1481
+ if ident, ok := s.Fun.(*ast.Ident); ok {
1482
+ // Look for calls to select-related functions or other async operations
1483
+ if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
1484
+ // Check if this function is from the context package (like context.Background, etc.)
1485
+ if obj.Pkg() != nil && obj.Pkg().Path() == "context" {
1486
+ hasAsync = true
1487
+ return false
1488
+ }
1489
+ }
1490
+ }
1491
+
1492
+ // Check if the method takes a context parameter, which usually indicates async behavior
1493
+ if len(s.Args) > 0 {
1494
+ for _, arg := range s.Args {
1495
+ if argType := pkg.TypesInfo.TypeOf(arg); argType != nil {
1496
+ if named, ok := argType.(*types.Named); ok {
1497
+ if named.Obj() != nil && named.Obj().Pkg() != nil &&
1498
+ named.Obj().Pkg().Path() == "context" && named.Obj().Name() == "Context" {
1499
+ hasAsync = true
1500
+ return false
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+ }
1506
+ }
1507
+
1508
+ return true
1509
+ })
1510
+
1511
+ return hasAsync
1512
+ }
1513
+
1388
1514
  // NeedsReflectionMetadata returns whether the given function node needs reflection type metadata
1389
1515
  func (a *Analysis) NeedsReflectionMetadata(node ast.Node) bool {
1390
1516
  if node == nil {
@@ -1581,3 +1707,484 @@ func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map
1581
1707
  // For now, we'll ignore them as they're less common in shadowing scenarios
1582
1708
  }
1583
1709
  }
1710
+
1711
+ // trackInterfaceImplementation records that a struct type implements an interface method
1712
+ func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func, isAsync bool) {
1713
+ key := InterfaceMethodKey{
1714
+ InterfaceType: interfaceType.String(),
1715
+ MethodName: method.Name(),
1716
+ }
1717
+
1718
+ implementation := ImplementationInfo{
1719
+ StructType: structType,
1720
+ Method: method,
1721
+ IsAsyncByFlow: isAsync,
1722
+ }
1723
+
1724
+ a.InterfaceImplementations[key] = append(a.InterfaceImplementations[key], implementation)
1725
+ }
1726
+
1727
+ // IsInterfaceMethodAsync determines if an interface method should be async based on its implementations
1728
+ func (a *Analysis) IsInterfaceMethodAsync(interfaceType *types.Interface, methodName string) bool {
1729
+ key := InterfaceMethodKey{
1730
+ InterfaceType: interfaceType.String(),
1731
+ MethodName: methodName,
1732
+ }
1733
+
1734
+ // Check if we've already computed this
1735
+ if result, exists := a.InterfaceMethodAsyncStatus[key]; exists {
1736
+ return result
1737
+ }
1738
+
1739
+ // Find all implementations of this interface method
1740
+ implementations, exists := a.InterfaceImplementations[key]
1741
+ if !exists {
1742
+ // No implementations found, default to sync
1743
+ a.InterfaceMethodAsyncStatus[key] = false
1744
+ return false
1745
+ }
1746
+
1747
+ // If ANY implementation is async, the interface method is async
1748
+ for _, impl := range implementations {
1749
+ if impl.IsAsyncByFlow {
1750
+ a.InterfaceMethodAsyncStatus[key] = true
1751
+ return true
1752
+ }
1753
+ }
1754
+
1755
+ // All implementations are sync
1756
+ a.InterfaceMethodAsyncStatus[key] = false
1757
+ return false
1758
+ }
1759
+
1760
+ // MustBeAsyncDueToInterface checks if a struct method must be async due to interface constraints
1761
+ func (a *Analysis) MustBeAsyncDueToInterface(structType *types.Named, methodName string) bool {
1762
+ // Find all interfaces that this struct implements
1763
+ for key, implementations := range a.InterfaceImplementations {
1764
+ if key.MethodName != methodName {
1765
+ continue
1766
+ }
1767
+
1768
+ // Check if this struct is among the implementations
1769
+ for _, impl := range implementations {
1770
+ if impl.StructType == structType {
1771
+ // This struct implements this interface method
1772
+ // Check if the interface method is marked as async
1773
+ interfaceType := a.findInterfaceTypeByString(key.InterfaceType)
1774
+ if interfaceType != nil && a.IsInterfaceMethodAsync(interfaceType, methodName) {
1775
+ return true
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1780
+
1781
+ return false
1782
+ }
1783
+
1784
+ // findInterfaceTypeByString finds an interface type by its string representation
1785
+ // This is a helper method for MustBeAsyncDueToInterface
1786
+ func (a *Analysis) findInterfaceTypeByString(interfaceString string) *types.Interface {
1787
+ // This is a simplified implementation - in practice, we might need to store
1788
+ // the actual interface types in our tracking data structure
1789
+ for _, pkg := range a.AllPackages {
1790
+ for _, syntax := range pkg.Syntax {
1791
+ for _, decl := range syntax.Decls {
1792
+ if genDecl, ok := decl.(*ast.GenDecl); ok {
1793
+ for _, spec := range genDecl.Specs {
1794
+ if typeSpec, ok := spec.(*ast.TypeSpec); ok {
1795
+ if interfaceType, ok := typeSpec.Type.(*ast.InterfaceType); ok {
1796
+ if goType := pkg.TypesInfo.TypeOf(interfaceType); goType != nil {
1797
+ if iface, ok := goType.(*types.Interface); ok {
1798
+ if iface.String() == interfaceString {
1799
+ return iface
1800
+ }
1801
+ }
1802
+ }
1803
+ }
1804
+ }
1805
+ }
1806
+ }
1807
+ }
1808
+ }
1809
+ }
1810
+ return nil
1811
+ }
1812
+
1813
+ // GetReceiverMapping returns the receiver variable mapping for a function declaration
1814
+ func (a *Analysis) GetReceiverMapping(funcDecl *ast.FuncDecl) string {
1815
+ if funcDecl.Recv != nil && len(funcDecl.Recv.List) > 0 {
1816
+ for _, field := range funcDecl.Recv.List {
1817
+ for _, name := range field.Names {
1818
+ if name != nil && name.Name != "_" {
1819
+ return "receiver"
1820
+ }
1821
+ }
1822
+ }
1823
+ }
1824
+ return ""
1825
+ }
1826
+
1827
+ // GetIdentifierMapping returns the replacement name for an identifier
1828
+ func (a *Analysis) GetIdentifierMapping(ident *ast.Ident) string {
1829
+ if ident == nil {
1830
+ return ""
1831
+ }
1832
+
1833
+ // Check if this identifier has a mapping in NodeData
1834
+ if nodeInfo := a.NodeData[ident]; nodeInfo != nil {
1835
+ return nodeInfo.IdentifierMapping
1836
+ }
1837
+
1838
+ return ""
1839
+ }
1840
+
1841
+ // trackTypeAssertion analyzes type assertions and records interface implementations
1842
+ func (v *analysisVisitor) trackTypeAssertion(typeAssert *ast.TypeAssertExpr) {
1843
+ // Get the type being asserted to
1844
+ assertedType := v.pkg.TypesInfo.TypeOf(typeAssert.Type)
1845
+ if assertedType == nil {
1846
+ return
1847
+ }
1848
+
1849
+ // Check if the asserted type is an interface
1850
+ interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
1851
+ if !isInterface {
1852
+ return
1853
+ }
1854
+
1855
+ // Get the type of the expression being asserted
1856
+ exprType := v.pkg.TypesInfo.TypeOf(typeAssert.X)
1857
+ if exprType == nil {
1858
+ return
1859
+ }
1860
+
1861
+ // Handle pointer types by getting the element type
1862
+ if ptrType, isPtr := exprType.(*types.Pointer); isPtr {
1863
+ exprType = ptrType.Elem()
1864
+ }
1865
+
1866
+ // Check if the expression type is a named struct type
1867
+ namedType, isNamed := exprType.(*types.Named)
1868
+ if !isNamed {
1869
+ return
1870
+ }
1871
+
1872
+ // For each method in the interface, check if the struct implements it
1873
+ for i := 0; i < interfaceType.NumExplicitMethods(); i++ {
1874
+ interfaceMethod := interfaceType.ExplicitMethod(i)
1875
+
1876
+ // Find the corresponding method in the struct type
1877
+ structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
1878
+ if structMethod != nil {
1879
+ // Determine if this struct method is async based on control flow analysis
1880
+ isAsync := false
1881
+ if obj := structMethod; obj != nil {
1882
+ if funcInfo := v.analysis.FunctionData[obj]; funcInfo != nil {
1883
+ isAsync = funcInfo.IsAsync
1884
+ }
1885
+ }
1886
+
1887
+ // Track this interface implementation
1888
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
1889
+ }
1890
+ }
1891
+ }
1892
+
1893
+ // findStructMethod finds a method with the given name on a named type
1894
+ func (v *analysisVisitor) findStructMethod(namedType *types.Named, methodName string) *types.Func {
1895
+ // Check methods directly on the type
1896
+ for i := 0; i < namedType.NumMethods(); i++ {
1897
+ method := namedType.Method(i)
1898
+ if method.Name() == methodName {
1899
+ return method
1900
+ }
1901
+ }
1902
+ return nil
1903
+ }
1904
+
1905
+ // analyzeAssignment analyzes a single assignment for pointer analysis
1906
+ func (v *analysisVisitor) analyzeAssignment(lhsExpr, rhsExpr ast.Expr) {
1907
+ // Determine RHS assignment type and source object
1908
+ rhsAssignmentType := DirectAssignment
1909
+ var rhsSourceObj types.Object
1910
+
1911
+ if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
1912
+ // RHS is &some_expr
1913
+ rhsAssignmentType = AddressOfAssignment
1914
+ if rhsIdent, ok := unaryExpr.X.(*ast.Ident); ok {
1915
+ rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
1916
+ }
1917
+ } else if rhsIdent, ok := rhsExpr.(*ast.Ident); ok {
1918
+ // RHS is variable
1919
+ rhsAssignmentType = DirectAssignment
1920
+ rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
1921
+ }
1922
+
1923
+ // Determine LHS object
1924
+ var lhsTrackedObj types.Object
1925
+
1926
+ if lhsIdent, ok := lhsExpr.(*ast.Ident); ok {
1927
+ if lhsIdent.Name != "_" {
1928
+ lhsTrackedObj = v.pkg.TypesInfo.ObjectOf(lhsIdent)
1929
+ }
1930
+ } else if selExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
1931
+ if selection := v.pkg.TypesInfo.Selections[selExpr]; selection != nil {
1932
+ lhsTrackedObj = selection.Obj()
1933
+ }
1934
+ }
1935
+
1936
+ // Record usage information
1937
+ if _, isVar := lhsTrackedObj.(*types.Var); isVar {
1938
+ lhsUsageInfo := v.getOrCreateUsageInfo(lhsTrackedObj)
1939
+ if rhsSourceObj != nil {
1940
+ lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
1941
+ Object: rhsSourceObj,
1942
+ Type: rhsAssignmentType,
1943
+ })
1944
+ } else if rhsAssignmentType == AddressOfAssignment {
1945
+ lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
1946
+ Object: nil,
1947
+ Type: rhsAssignmentType,
1948
+ })
1949
+ }
1950
+ }
1951
+
1952
+ if rhsSourceObj != nil {
1953
+ sourceUsageInfo := v.getOrCreateUsageInfo(rhsSourceObj)
1954
+ sourceUsageInfo.Destinations = append(sourceUsageInfo.Destinations, AssignmentInfo{
1955
+ Object: lhsTrackedObj,
1956
+ Type: rhsAssignmentType,
1957
+ })
1958
+ }
1959
+ }
1960
+
1961
+ // trackInterfaceAssignments tracks interface implementations in assignment statements
1962
+ func (v *analysisVisitor) trackInterfaceAssignments(assignStmt *ast.AssignStmt) {
1963
+ // For each assignment, check if we're assigning a struct to an interface variable
1964
+ for i, lhsExpr := range assignStmt.Lhs {
1965
+ if i >= len(assignStmt.Rhs) {
1966
+ continue
1967
+ }
1968
+ rhsExpr := assignStmt.Rhs[i]
1969
+
1970
+ // Get the type of the LHS (destination)
1971
+ lhsType := v.pkg.TypesInfo.TypeOf(lhsExpr)
1972
+ if lhsType == nil {
1973
+ continue
1974
+ }
1975
+
1976
+ // Check if LHS is an interface type
1977
+ interfaceType, isInterface := lhsType.Underlying().(*types.Interface)
1978
+ if !isInterface {
1979
+ continue
1980
+ }
1981
+
1982
+ // Get the type of the RHS (source)
1983
+ rhsType := v.pkg.TypesInfo.TypeOf(rhsExpr)
1984
+ if rhsType == nil {
1985
+ continue
1986
+ }
1987
+
1988
+ // Handle pointer types
1989
+ if ptrType, isPtr := rhsType.(*types.Pointer); isPtr {
1990
+ rhsType = ptrType.Elem()
1991
+ }
1992
+
1993
+ // Check if RHS is a named struct type
1994
+ namedType, isNamed := rhsType.(*types.Named)
1995
+ if !isNamed {
1996
+ continue
1997
+ }
1998
+
1999
+ // Track implementations for all interface methods
2000
+ for j := 0; j < interfaceType.NumExplicitMethods(); j++ {
2001
+ interfaceMethod := interfaceType.ExplicitMethod(j)
2002
+
2003
+ structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
2004
+ if structMethod != nil {
2005
+ // Determine if this struct method is async
2006
+ isAsync := false
2007
+ if funcInfo := v.analysis.FunctionData[structMethod]; funcInfo != nil {
2008
+ isAsync = funcInfo.IsAsync
2009
+ }
2010
+
2011
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
2012
+ }
2013
+ }
2014
+ }
2015
+ }
2016
+
2017
+ // IsNamedBasicType returns whether the given type should be implemented as a type alias with standalone functions
2018
+ // This applies to named types with basic underlying types (like uint32, string, etc.) that have methods
2019
+ // It excludes struct types, which should remain as classes
2020
+ func (a *Analysis) IsNamedBasicType(t types.Type) bool {
2021
+ if t == nil {
2022
+ return false
2023
+ }
2024
+
2025
+ // Check if we already have this result cached
2026
+ if result, exists := a.NamedBasicTypes[t]; exists {
2027
+ return result
2028
+ }
2029
+
2030
+ var originalType types.Type = t
2031
+ var foundMethods bool
2032
+
2033
+ // Traverse the type chain to find any type with methods
2034
+ for {
2035
+ switch typed := t.(type) {
2036
+ case *types.Named:
2037
+ // Built-in types cannot be named basic types
2038
+ if typed.Obj().Pkg() == nil {
2039
+ return false
2040
+ }
2041
+
2042
+ // Check if this named type has methods
2043
+ if typed.NumMethods() > 0 {
2044
+ foundMethods = true
2045
+ }
2046
+
2047
+ // Check underlying type
2048
+ underlying := typed.Underlying()
2049
+ switch underlying.(type) {
2050
+ case *types.Struct, *types.Interface:
2051
+ return false
2052
+ }
2053
+ t = underlying
2054
+
2055
+ case *types.Alias:
2056
+ // Built-in types cannot be named basic types
2057
+ if typed.Obj().Pkg() == nil {
2058
+ return false
2059
+ }
2060
+ t = typed.Underlying()
2061
+
2062
+ default:
2063
+ // We've reached a non-named, non-alias type
2064
+ // Check if it's a supported type with methods
2065
+ switch t.(type) {
2066
+ case *types.Basic, *types.Slice, *types.Array, *types.Map:
2067
+ if foundMethods {
2068
+ a.NamedBasicTypes[originalType] = true
2069
+ return true
2070
+ }
2071
+ return false
2072
+ default:
2073
+ return false
2074
+ }
2075
+ }
2076
+ }
2077
+ }
2078
+
2079
+ // interfaceImplementationVisitor performs a second pass to analyze interface implementations
2080
+ type interfaceImplementationVisitor struct {
2081
+ analysis *Analysis
2082
+ pkg *packages.Package
2083
+ }
2084
+
2085
+ func (v *interfaceImplementationVisitor) Visit(node ast.Node) ast.Visitor {
2086
+ switch n := node.(type) {
2087
+ case *ast.GenDecl:
2088
+ // Look for interface type specifications
2089
+ for _, spec := range n.Specs {
2090
+ if typeSpec, ok := spec.(*ast.TypeSpec); ok {
2091
+ if interfaceType, ok := typeSpec.Type.(*ast.InterfaceType); ok {
2092
+ // This is an interface declaration, find all potential implementations
2093
+ v.findInterfaceImplementations(interfaceType)
2094
+ }
2095
+ }
2096
+ }
2097
+ }
2098
+ return v
2099
+ }
2100
+
2101
+ // findInterfaceImplementations finds all struct types that implement the given interface
2102
+ func (v *interfaceImplementationVisitor) findInterfaceImplementations(interfaceAST *ast.InterfaceType) {
2103
+ // Get the interface type from TypesInfo
2104
+ interfaceGoType := v.pkg.TypesInfo.TypeOf(interfaceAST)
2105
+ if interfaceGoType == nil {
2106
+ return
2107
+ }
2108
+
2109
+ interfaceType, ok := interfaceGoType.(*types.Interface)
2110
+ if !ok {
2111
+ return
2112
+ }
2113
+
2114
+ // Look through all packages for potential implementations
2115
+ for _, pkg := range v.analysis.AllPackages {
2116
+ v.findImplementationsInPackage(interfaceType, pkg)
2117
+ }
2118
+ }
2119
+
2120
+ // findImplementationsInPackage finds implementations of an interface in a specific package
2121
+ func (v *interfaceImplementationVisitor) findImplementationsInPackage(interfaceType *types.Interface, pkg *packages.Package) {
2122
+ // Get all named types in the package
2123
+ scope := pkg.Types.Scope()
2124
+ for _, name := range scope.Names() {
2125
+ obj := scope.Lookup(name)
2126
+ if obj == nil {
2127
+ continue
2128
+ }
2129
+
2130
+ // Check if this is a type name
2131
+ typeName, ok := obj.(*types.TypeName)
2132
+ if !ok {
2133
+ continue
2134
+ }
2135
+
2136
+ namedType, ok := typeName.Type().(*types.Named)
2137
+ if !ok {
2138
+ continue
2139
+ }
2140
+
2141
+ // Check if this type implements the interface
2142
+ if types.Implements(namedType, interfaceType) || types.Implements(types.NewPointer(namedType), interfaceType) {
2143
+ v.trackImplementation(interfaceType, namedType)
2144
+ }
2145
+ }
2146
+ }
2147
+
2148
+ // trackImplementation records that a named type implements an interface
2149
+ func (v *interfaceImplementationVisitor) trackImplementation(interfaceType *types.Interface, namedType *types.Named) {
2150
+ // For each method in the interface, find the corresponding implementation
2151
+ for i := 0; i < interfaceType.NumExplicitMethods(); i++ {
2152
+ interfaceMethod := interfaceType.ExplicitMethod(i)
2153
+
2154
+ // Find the method in the implementing type
2155
+ structMethod := v.findMethodInType(namedType, interfaceMethod.Name())
2156
+ if structMethod != nil {
2157
+ // Determine if this implementation is async
2158
+ isAsync := false
2159
+ if funcInfo := v.analysis.FunctionData[structMethod]; funcInfo != nil {
2160
+ isAsync = funcInfo.IsAsync
2161
+ }
2162
+
2163
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
2164
+ }
2165
+ }
2166
+ }
2167
+
2168
+ // findMethodInType finds a method with the given name in a named type
2169
+ func (v *interfaceImplementationVisitor) findMethodInType(namedType *types.Named, methodName string) *types.Func {
2170
+ for i := 0; i < namedType.NumMethods(); i++ {
2171
+ method := namedType.Method(i)
2172
+ if method.Name() == methodName {
2173
+ return method
2174
+ }
2175
+ }
2176
+ return nil
2177
+ }
2178
+
2179
+ // getNamedReturns retrieves the named returns for a function
2180
+ func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
2181
+ var namedReturns []string
2182
+ if funcDecl.Type != nil && funcDecl.Type.Results != nil {
2183
+ for _, field := range funcDecl.Type.Results.List {
2184
+ for _, name := range field.Names {
2185
+ namedReturns = append(namedReturns, name.Name)
2186
+ }
2187
+ }
2188
+ }
2189
+ return namedReturns
2190
+ }