goscript 0.0.60 → 0.0.62

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 (87) hide show
  1. package/README.md +9 -0
  2. package/compiler/analysis.go +974 -369
  3. package/compiler/assignment.go +72 -0
  4. package/compiler/compiler.go +74 -15
  5. package/compiler/composite-lit.go +29 -8
  6. package/compiler/decl.go +67 -98
  7. package/compiler/expr-call-async.go +26 -1
  8. package/compiler/expr-call-builtins.go +60 -4
  9. package/compiler/expr-call-helpers.go +182 -0
  10. package/compiler/expr-call-type-conversion.go +37 -5
  11. package/compiler/expr-call.go +25 -33
  12. package/compiler/expr-selector.go +71 -1
  13. package/compiler/expr-type.go +49 -3
  14. package/compiler/expr.go +37 -28
  15. package/compiler/index.test.ts +3 -1
  16. package/compiler/lit.go +13 -4
  17. package/compiler/spec-struct.go +42 -9
  18. package/compiler/spec-value.go +2 -2
  19. package/compiler/spec.go +42 -5
  20. package/compiler/stmt-assign.go +71 -0
  21. package/compiler/stmt-range.go +2 -2
  22. package/compiler/stmt.go +130 -10
  23. package/compiler/type-utils.go +40 -16
  24. package/compiler/type.go +50 -12
  25. package/dist/gs/builtin/builtin.d.ts +8 -1
  26. package/dist/gs/builtin/builtin.js +26 -1
  27. package/dist/gs/builtin/builtin.js.map +1 -1
  28. package/dist/gs/builtin/errors.d.ts +1 -0
  29. package/dist/gs/builtin/errors.js +8 -0
  30. package/dist/gs/builtin/errors.js.map +1 -1
  31. package/dist/gs/builtin/slice.d.ts +5 -4
  32. package/dist/gs/builtin/slice.js +51 -21
  33. package/dist/gs/builtin/slice.js.map +1 -1
  34. package/dist/gs/builtin/type.d.ts +28 -2
  35. package/dist/gs/builtin/type.js +132 -0
  36. package/dist/gs/builtin/type.js.map +1 -1
  37. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  38. package/dist/gs/bytes/reader.gs.js +1 -1
  39. package/dist/gs/bytes/reader.gs.js.map +1 -1
  40. package/dist/gs/internal/byteorder/index.d.ts +6 -0
  41. package/dist/gs/internal/byteorder/index.js +34 -0
  42. package/dist/gs/internal/byteorder/index.js.map +1 -1
  43. package/dist/gs/reflect/index.d.ts +3 -3
  44. package/dist/gs/reflect/index.js +2 -2
  45. package/dist/gs/reflect/index.js.map +1 -1
  46. package/dist/gs/reflect/map.d.ts +3 -2
  47. package/dist/gs/reflect/map.js +37 -3
  48. package/dist/gs/reflect/map.js.map +1 -1
  49. package/dist/gs/reflect/type.d.ts +55 -8
  50. package/dist/gs/reflect/type.js +889 -23
  51. package/dist/gs/reflect/type.js.map +1 -1
  52. package/dist/gs/reflect/types.d.ts +11 -12
  53. package/dist/gs/reflect/types.js +26 -15
  54. package/dist/gs/reflect/types.js.map +1 -1
  55. package/dist/gs/reflect/value.d.ts +4 -4
  56. package/dist/gs/reflect/value.js +8 -2
  57. package/dist/gs/reflect/value.js.map +1 -1
  58. package/dist/gs/slices/slices.d.ts +32 -0
  59. package/dist/gs/slices/slices.js +81 -0
  60. package/dist/gs/slices/slices.js.map +1 -1
  61. package/dist/gs/sync/atomic/type.gs.d.ts +2 -2
  62. package/dist/gs/sync/atomic/type.gs.js +12 -2
  63. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  64. package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
  65. package/dist/gs/unicode/utf8/utf8.js +10 -6
  66. package/dist/gs/unicode/utf8/utf8.js.map +1 -1
  67. package/go.mod +4 -4
  68. package/go.sum +8 -16
  69. package/gs/builtin/builtin.ts +27 -2
  70. package/gs/builtin/errors.ts +12 -0
  71. package/gs/builtin/slice.ts +77 -14
  72. package/gs/builtin/type.ts +167 -2
  73. package/gs/bytes/reader.gs.ts +2 -2
  74. package/gs/internal/byteorder/index.ts +40 -0
  75. package/gs/math/hypot.gs.test.ts +3 -1
  76. package/gs/math/pow10.gs.test.ts +5 -4
  77. package/gs/reflect/index.ts +6 -3
  78. package/gs/reflect/map.test.ts +7 -6
  79. package/gs/reflect/map.ts +49 -7
  80. package/gs/reflect/type.ts +1139 -43
  81. package/gs/reflect/types.ts +34 -21
  82. package/gs/reflect/value.ts +12 -6
  83. package/gs/slices/slices.ts +92 -0
  84. package/gs/sync/atomic/type.gs.ts +14 -5
  85. package/gs/sync/meta.json +1 -1
  86. package/gs/unicode/utf8/utf8.ts +12 -8
  87. package/package.json +13 -13
@@ -6,6 +6,7 @@ import (
6
6
  "go/token"
7
7
  "go/types"
8
8
  "path/filepath"
9
+ "slices"
9
10
  "strings"
10
11
 
11
12
  "github.com/aperturerobotics/goscript"
@@ -51,6 +52,10 @@ type ShadowingInfo struct {
51
52
  ShadowedVariables map[string]types.Object
52
53
  // TempVariables maps shadowed variable names to temporary variable names
53
54
  TempVariables map[string]string
55
+ // TypeShadowedVars maps variable names that shadow type names to their renamed identifier
56
+ // This happens when a variable name matches a type name used in its initialization
57
+ // e.g., field := field{...} where the variable 'field' shadows the type 'field'
58
+ TypeShadowedVars map[string]string
54
59
  }
55
60
 
56
61
  // FunctionTypeInfo represents Go function type information for reflection
@@ -106,9 +111,8 @@ type MethodKey struct {
106
111
 
107
112
  // ImplementationInfo tracks information about a struct that implements an interface method
108
113
  type ImplementationInfo struct {
109
- StructType *types.Named // The struct type that implements the interface
110
- Method *types.Func // The method object
111
- IsAsyncByFlow bool // Whether this implementation is async based on control flow analysis
114
+ StructType *types.Named // The struct type that implements the interface
115
+ Method *types.Func // The method object
112
116
  }
113
117
 
114
118
  // Analysis holds information gathered during the analysis phase of the Go code compilation.
@@ -122,6 +126,10 @@ type Analysis struct {
122
126
  // Imports stores the imports for the file
123
127
  Imports map[string]*fileImport
124
128
 
129
+ // SyntheticImports stores imports that need to be written but were not in the source file.
130
+ // These are typically needed for promoted methods from embedded structs.
131
+ SyntheticImports map[string]*fileImport
132
+
125
133
  // Cmap stores the comment map for the file
126
134
  Cmap ast.CommentMap
127
135
 
@@ -141,6 +149,11 @@ type Analysis struct {
141
149
  // FunctionAssignments tracks which function literals are assigned to which variables
142
150
  FunctionAssignments map[types.Object]ast.Node
143
151
 
152
+ // AsyncReturningVars tracks variables whose function type returns async values
153
+ // This happens when a variable is assigned from a higher-order function (like sync.OnceValue)
154
+ // that receives an async function literal as an argument
155
+ AsyncReturningVars map[types.Object]bool
156
+
144
157
  // NamedBasicTypes tracks types that should be implemented as type aliases with standalone functions
145
158
  // This includes named types with basic underlying types (like uint32, string) that have methods
146
159
  NamedBasicTypes map[types.Type]bool
@@ -153,10 +166,6 @@ type Analysis struct {
153
166
  // This is used to determine interface method async status based on implementations
154
167
  InterfaceImplementations map[InterfaceMethodKey][]ImplementationInfo
155
168
 
156
- // InterfaceMethodAsyncStatus caches the async status determination for interface methods
157
- // This is computed once during analysis and reused during code generation
158
- InterfaceMethodAsyncStatus map[InterfaceMethodKey]bool
159
-
160
169
  // MethodAsyncStatus stores the async status of all methods analyzed
161
170
  // This is computed once during analysis and reused during code generation
162
171
  MethodAsyncStatus map[MethodKey]bool
@@ -179,6 +188,14 @@ type PackageAnalysis struct {
179
188
  // TypeCalls maps file names to the types they reference from other files
180
189
  // Key: filename (without .go extension), Value: map[sourceFile][]typeNames
181
190
  TypeCalls map[string]map[string][]string
191
+
192
+ // VariableDefs maps file names to the package-level variables defined in that file
193
+ // Key: filename (without .go extension), Value: list of variable names
194
+ VariableDefs map[string][]string
195
+
196
+ // VariableCalls maps file names to the package-level variables they reference from other files
197
+ // Key: filename (without .go extension), Value: map[sourceFile][]variableNames
198
+ VariableCalls map[string]map[string][]string
182
199
  }
183
200
 
184
201
  // NewAnalysis creates a new Analysis instance.
@@ -188,19 +205,19 @@ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
188
205
  }
189
206
 
190
207
  return &Analysis{
191
- VariableUsage: make(map[types.Object]*VariableUsageInfo),
192
- Imports: make(map[string]*fileImport),
193
- FunctionData: make(map[types.Object]*FunctionInfo),
194
- NodeData: make(map[ast.Node]*NodeInfo),
195
- FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
196
- ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
197
- FunctionAssignments: make(map[types.Object]ast.Node),
198
- // PackageMetadata removed - using MethodAsyncStatus only
199
- NamedBasicTypes: make(map[types.Type]bool),
200
- AllPackages: allPackages,
201
- InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
202
- InterfaceMethodAsyncStatus: make(map[InterfaceMethodKey]bool),
203
- MethodAsyncStatus: make(map[MethodKey]bool),
208
+ VariableUsage: make(map[types.Object]*VariableUsageInfo),
209
+ Imports: make(map[string]*fileImport),
210
+ SyntheticImports: make(map[string]*fileImport),
211
+ FunctionData: make(map[types.Object]*FunctionInfo),
212
+ NodeData: make(map[ast.Node]*NodeInfo),
213
+ FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
214
+ ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
215
+ FunctionAssignments: make(map[types.Object]ast.Node),
216
+ AsyncReturningVars: make(map[types.Object]bool),
217
+ NamedBasicTypes: make(map[types.Type]bool),
218
+ AllPackages: allPackages,
219
+ InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
220
+ MethodAsyncStatus: make(map[MethodKey]bool),
204
221
  }
205
222
  }
206
223
 
@@ -211,6 +228,8 @@ func NewPackageAnalysis() *PackageAnalysis {
211
228
  FunctionCalls: make(map[string]map[string][]string),
212
229
  TypeDefs: make(map[string][]string),
213
230
  TypeCalls: make(map[string]map[string][]string),
231
+ VariableDefs: make(map[string][]string),
232
+ VariableCalls: make(map[string]map[string][]string),
214
233
  }
215
234
  }
216
235
 
@@ -236,6 +255,22 @@ func (a *Analysis) ensureFunctionData(obj types.Object) *FunctionInfo {
236
255
  return a.FunctionData[obj]
237
256
  }
238
257
 
258
+ // GetFunctionInfoFromContext returns FunctionInfo based on the enclosing function context
259
+ func (a *Analysis) GetFunctionInfoFromContext(nodeInfo *NodeInfo, pkg *packages.Package) *FunctionInfo {
260
+ if nodeInfo == nil {
261
+ return nil
262
+ }
263
+ if nodeInfo.EnclosingFuncDecl != nil {
264
+ if obj := pkg.TypesInfo.ObjectOf(nodeInfo.EnclosingFuncDecl.Name); obj != nil {
265
+ return a.FunctionData[obj]
266
+ }
267
+ }
268
+ if nodeInfo.EnclosingFuncLit != nil {
269
+ return a.FuncLitData[nodeInfo.EnclosingFuncLit]
270
+ }
271
+ return nil
272
+ }
273
+
239
274
  // NeedsDefer returns whether the given node needs defer handling.
240
275
  func (a *Analysis) NeedsDefer(node ast.Node) bool {
241
276
  if node == nil {
@@ -302,6 +337,15 @@ func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
302
337
  return false
303
338
  }
304
339
 
340
+ // IsAsyncReturningVar returns whether the given variable holds a function that returns async values.
341
+ // This is true when the variable is assigned from a higher-order function that receives an async function literal.
342
+ func (a *Analysis) IsAsyncReturningVar(obj types.Object) bool {
343
+ if obj == nil {
344
+ return false
345
+ }
346
+ return a.AsyncReturningVars[obj]
347
+ }
348
+
305
349
  func (a *Analysis) IsReceiverUsed(obj types.Object) bool {
306
350
  if obj == nil {
307
351
  return false
@@ -370,7 +414,7 @@ func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
370
414
  }
371
415
 
372
416
  // For pointer variables, check if they point to a variable-referenced value
373
- if ptrType, ok := obj.Type().(*types.Pointer); ok {
417
+ if _, ok := obj.Type().(*types.Pointer); ok {
374
418
  // Check all assignments to this pointer variable
375
419
  for varObj, info := range a.VariableUsage {
376
420
  if varObj == obj {
@@ -382,11 +426,6 @@ func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
382
426
  }
383
427
  }
384
428
  }
385
-
386
- // Handle direct pointer initialization like: var p *int = &x
387
- // Check if the pointer type's element type requires variable referencing
388
- _ = ptrType.Elem()
389
- // For now, conservatively return false for untracked cases
390
429
  }
391
430
 
392
431
  return false
@@ -453,9 +492,6 @@ type analysisVisitor struct {
453
492
 
454
493
  // currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
455
494
  currentFuncLit *ast.FuncLit
456
-
457
- // visitingMethods tracks methods currently being analyzed to prevent infinite recursion
458
- visitingMethods map[MethodKey]bool
459
495
  }
460
496
 
461
497
  // getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
@@ -906,9 +942,63 @@ func (v *analysisVisitor) visitAssignStmt(n *ast.AssignStmt) ast.Visitor {
906
942
  }
907
943
  }
908
944
 
945
+ // NOTE: Async-returning variable tracking (trackAsyncReturningVar) is done in a separate pass
946
+ // after function literals are analyzed for async status. See trackAsyncReturningVarsAllFiles.
947
+
909
948
  return v
910
949
  }
911
950
 
951
+ // trackAsyncReturningVar tracks variables that are assigned from higher-order function calls
952
+ // where one of the arguments is an async function literal.
953
+ // Pattern: x := higherOrderFunc(asyncFuncLit)
954
+ // This is needed because when sync.OnceValue(asyncFunc) is called, the result is a function
955
+ // that returns a Promise, and callers of x() need to await the result.
956
+ func (v *analysisVisitor) trackAsyncReturningVar(lhs ast.Expr, rhs ast.Expr) {
957
+ // LHS must be an identifier
958
+ lhsIdent, ok := lhs.(*ast.Ident)
959
+ if !ok {
960
+ return
961
+ }
962
+
963
+ // RHS must be a call expression
964
+ callExpr, ok := rhs.(*ast.CallExpr)
965
+ if !ok {
966
+ return
967
+ }
968
+
969
+ // The result type of the call must be a function type
970
+ rhsType := v.pkg.TypesInfo.TypeOf(rhs)
971
+ if rhsType == nil {
972
+ return
973
+ }
974
+ _, isFunc := rhsType.Underlying().(*types.Signature)
975
+ if !isFunc {
976
+ return
977
+ }
978
+
979
+ // Check if any argument is an async function literal
980
+ // Use containsAsyncOperationsComplete to check the function body directly
981
+ // rather than relying on the InAsyncContext flag which may not be set yet
982
+ hasAsyncArg := false
983
+ for _, arg := range callExpr.Args {
984
+ if funcLit, ok := arg.(*ast.FuncLit); ok {
985
+ if funcLit.Body != nil && v.containsAsyncOperationsComplete(funcLit.Body, v.pkg) {
986
+ hasAsyncArg = true
987
+ break
988
+ }
989
+ }
990
+ }
991
+
992
+ if !hasAsyncArg {
993
+ return
994
+ }
995
+
996
+ // Mark the LHS variable as returning async values
997
+ if obj := v.pkg.TypesInfo.ObjectOf(lhsIdent); obj != nil {
998
+ v.analysis.AsyncReturningVars[obj] = true
999
+ }
1000
+ }
1001
+
912
1002
  // visitReturnStmt handles return statement analysis
913
1003
  func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
914
1004
  nodeInfo := v.analysis.ensureNodeData(n)
@@ -922,18 +1012,8 @@ func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
922
1012
 
923
1013
  // Check if it's a bare return
924
1014
  if len(n.Results) == 0 {
925
- if v.currentFuncDecl != nil {
926
- // Check if the enclosing function declaration has named returns
927
- if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
928
- if _, ok := v.analysis.FunctionData[obj]; ok {
929
- nodeInfo.IsBareReturn = true
930
- }
931
- }
932
- } else if v.currentFuncLit != nil {
933
- // Check if the enclosing function literal has named returns
934
- if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
935
- nodeInfo.IsBareReturn = true
936
- }
1015
+ if v.analysis.GetFunctionInfoFromContext(nodeInfo, v.pkg) != nil {
1016
+ nodeInfo.IsBareReturn = true
937
1017
  }
938
1018
  }
939
1019
  return v
@@ -1011,14 +1091,8 @@ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) as
1011
1091
  // Find the corresponding method in the struct type
1012
1092
  structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
1013
1093
  if structMethod != nil {
1014
- // Determine if this struct method is async using unified system
1015
- isAsync := false
1016
- if obj := structMethod; obj != nil {
1017
- isAsync = v.analysis.IsAsyncFunc(obj)
1018
- }
1019
-
1020
1094
  // Track this interface implementation
1021
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
1095
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
1022
1096
  }
1023
1097
  }
1024
1098
  return v
@@ -1182,9 +1256,8 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
1182
1256
 
1183
1257
  // Create visitor for the entire package
1184
1258
  visitor := &analysisVisitor{
1185
- analysis: analysis,
1186
- pkg: pkg,
1187
- visitingMethods: make(map[MethodKey]bool),
1259
+ analysis: analysis,
1260
+ pkg: pkg,
1188
1261
  }
1189
1262
 
1190
1263
  // First pass: analyze all declarations and statements across all files
@@ -1221,9 +1294,165 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
1221
1294
  // Interface implementation async status is now updated on-demand in IsInterfaceMethodAsync
1222
1295
  visitor.analyzeAllMethodsAsync()
1223
1296
 
1297
+ // Fourth pass: collect imports needed by promoted methods from embedded structs
1298
+ analysis.addImportsForPromotedMethods(pkg)
1299
+
1224
1300
  return analysis
1225
1301
  }
1226
1302
 
1303
+ // addImportsForPromotedMethods scans all struct types in the package for embedded fields
1304
+ // and adds imports for any packages referenced by the promoted methods' parameter/return types.
1305
+ func (a *Analysis) addImportsForPromotedMethods(pkg *packages.Package) {
1306
+ // Collect all package names we need to add
1307
+ packagesToAdd := make(map[string]*types.Package)
1308
+
1309
+ // Iterate through all type definitions in the package
1310
+ scope := pkg.Types.Scope()
1311
+ for _, name := range scope.Names() {
1312
+ obj := scope.Lookup(name)
1313
+ if obj == nil {
1314
+ continue
1315
+ }
1316
+
1317
+ // Check if it's a type definition
1318
+ typeName, ok := obj.(*types.TypeName)
1319
+ if !ok {
1320
+ continue
1321
+ }
1322
+
1323
+ // Get the underlying type
1324
+ namedType, ok := typeName.Type().(*types.Named)
1325
+ if !ok {
1326
+ continue
1327
+ }
1328
+
1329
+ // Check if it's a struct
1330
+ structType, ok := namedType.Underlying().(*types.Struct)
1331
+ if !ok {
1332
+ continue
1333
+ }
1334
+
1335
+ // Look for embedded fields
1336
+ for i := 0; i < structType.NumFields(); i++ {
1337
+ field := structType.Field(i)
1338
+ if !field.Embedded() {
1339
+ continue
1340
+ }
1341
+
1342
+ // Get the type of the embedded field
1343
+ embeddedType := field.Type()
1344
+
1345
+ // Handle pointer to embedded type
1346
+ if ptr, ok := embeddedType.(*types.Pointer); ok {
1347
+ embeddedType = ptr.Elem()
1348
+ }
1349
+
1350
+ // Use method set to get all promoted methods including pointer receiver methods
1351
+ // This matches Go's behavior where embedding T promotes both T and *T methods
1352
+ methodSetType := embeddedType
1353
+ if _, isPtr := embeddedType.(*types.Pointer); !isPtr {
1354
+ if _, isInterface := embeddedType.Underlying().(*types.Interface); !isInterface {
1355
+ methodSetType = types.NewPointer(embeddedType)
1356
+ }
1357
+ }
1358
+ embeddedMethodSet := types.NewMethodSet(methodSetType)
1359
+
1360
+ // Scan all methods in the method set
1361
+ for j := 0; j < embeddedMethodSet.Len(); j++ {
1362
+ selection := embeddedMethodSet.At(j)
1363
+ method := selection.Obj()
1364
+ sig, ok := method.Type().(*types.Signature)
1365
+ if !ok {
1366
+ continue
1367
+ }
1368
+
1369
+ // Scan parameters
1370
+ if sig.Params() != nil {
1371
+ for k := 0; k < sig.Params().Len(); k++ {
1372
+ param := sig.Params().At(k)
1373
+ a.collectPackageFromType(param.Type(), pkg.Types, packagesToAdd)
1374
+ }
1375
+ }
1376
+
1377
+ // Scan results
1378
+ if sig.Results() != nil {
1379
+ for k := 0; k < sig.Results().Len(); k++ {
1380
+ result := sig.Results().At(k)
1381
+ a.collectPackageFromType(result.Type(), pkg.Types, packagesToAdd)
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ // Add collected packages to imports (both regular and synthetic)
1389
+ // Always add to SyntheticImports - the file compiler will check if
1390
+ // the import was already written from the AST to avoid duplicates
1391
+ for pkgName, pkgObj := range packagesToAdd {
1392
+ tsImportPath := "@goscript/" + pkgObj.Path()
1393
+ fileImp := &fileImport{
1394
+ importPath: tsImportPath,
1395
+ importVars: make(map[string]struct{}),
1396
+ }
1397
+ // Add to SyntheticImports unconditionally
1398
+ a.SyntheticImports[pkgName] = fileImp
1399
+ // Also add to Imports if not already present
1400
+ if _, exists := a.Imports[pkgName]; !exists {
1401
+ a.Imports[pkgName] = fileImp
1402
+ }
1403
+ }
1404
+ }
1405
+
1406
+ // collectPackageFromType recursively collects packages referenced by a type.
1407
+ func (a *Analysis) collectPackageFromType(t types.Type, currentPkg *types.Package, packagesToAdd map[string]*types.Package) {
1408
+ switch typ := t.(type) {
1409
+ case *types.Named:
1410
+ pkg := typ.Obj().Pkg()
1411
+ if pkg != nil && pkg != currentPkg {
1412
+ packagesToAdd[pkg.Name()] = pkg
1413
+ }
1414
+ // Check type arguments for generics
1415
+ if typ.TypeArgs() != nil {
1416
+ for i := 0; i < typ.TypeArgs().Len(); i++ {
1417
+ a.collectPackageFromType(typ.TypeArgs().At(i), currentPkg, packagesToAdd)
1418
+ }
1419
+ }
1420
+ case *types.Interface:
1421
+ // For interfaces, we need to check embedded interfaces and method signatures
1422
+ for i := 0; i < typ.NumEmbeddeds(); i++ {
1423
+ a.collectPackageFromType(typ.EmbeddedType(i), currentPkg, packagesToAdd)
1424
+ }
1425
+ for i := 0; i < typ.NumExplicitMethods(); i++ {
1426
+ method := typ.ExplicitMethod(i)
1427
+ a.collectPackageFromType(method.Type(), currentPkg, packagesToAdd)
1428
+ }
1429
+ case *types.Pointer:
1430
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1431
+ case *types.Slice:
1432
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1433
+ case *types.Array:
1434
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1435
+ case *types.Map:
1436
+ a.collectPackageFromType(typ.Key(), currentPkg, packagesToAdd)
1437
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1438
+ case *types.Chan:
1439
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1440
+ case *types.Signature:
1441
+ // Collect from parameters
1442
+ if typ.Params() != nil {
1443
+ for i := 0; i < typ.Params().Len(); i++ {
1444
+ a.collectPackageFromType(typ.Params().At(i).Type(), currentPkg, packagesToAdd)
1445
+ }
1446
+ }
1447
+ // Collect from results
1448
+ if typ.Results() != nil {
1449
+ for i := 0; i < typ.Results().Len(); i++ {
1450
+ a.collectPackageFromType(typ.Results().At(i).Type(), currentPkg, packagesToAdd)
1451
+ }
1452
+ }
1453
+ }
1454
+ }
1455
+
1227
1456
  // AnalyzePackageImports performs package-level analysis to collect function definitions
1228
1457
  // and calls across all files in the package for auto-import generation
1229
1458
  func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
@@ -1235,19 +1464,50 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1235
1464
  baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
1236
1465
 
1237
1466
  var functions []string
1238
- var types []string
1467
+ var typeNames []string
1468
+ var variables []string
1239
1469
  for _, decl := range syntax.Decls {
1240
1470
  if funcDecl, ok := decl.(*ast.FuncDecl); ok {
1241
- // Only collect top-level functions (not methods)
1471
+ // Collect top-level functions (not methods)
1242
1472
  if funcDecl.Recv == nil {
1243
1473
  functions = append(functions, funcDecl.Name.Name)
1474
+ } else {
1475
+ // Check if this is a method on a wrapper type (named basic type)
1476
+ // If so, it will be compiled as TypeName_MethodName function
1477
+ if len(funcDecl.Recv.List) > 0 {
1478
+ recvType := funcDecl.Recv.List[0].Type
1479
+ // Handle pointer receiver (*Type)
1480
+ if starExpr, ok := recvType.(*ast.StarExpr); ok {
1481
+ recvType = starExpr.X
1482
+ }
1483
+ if recvIdent, ok := recvType.(*ast.Ident); ok {
1484
+ // Check if this receiver type is a wrapper type
1485
+ if obj := pkg.TypesInfo.Uses[recvIdent]; obj != nil {
1486
+ if typeName, ok := obj.(*types.TypeName); ok {
1487
+ if namedType, ok := typeName.Type().(*types.Named); ok {
1488
+ if _, ok := namedType.Underlying().(*types.Basic); ok {
1489
+ // This is a method on a wrapper type
1490
+ funcName := recvIdent.Name + "_" + funcDecl.Name.Name
1491
+ functions = append(functions, funcName)
1492
+ }
1493
+ }
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1244
1498
  }
1245
1499
  }
1246
1500
  if genDecl, ok := decl.(*ast.GenDecl); ok {
1247
1501
  // Collect type declarations
1248
1502
  for _, spec := range genDecl.Specs {
1249
1503
  if typeSpec, ok := spec.(*ast.TypeSpec); ok {
1250
- types = append(types, typeSpec.Name.Name)
1504
+ typeNames = append(typeNames, typeSpec.Name.Name)
1505
+ }
1506
+ // Collect variable/constant declarations
1507
+ if valueSpec, ok := spec.(*ast.ValueSpec); ok {
1508
+ for _, name := range valueSpec.Names {
1509
+ variables = append(variables, name.Name)
1510
+ }
1251
1511
  }
1252
1512
  }
1253
1513
  }
@@ -1256,8 +1516,11 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1256
1516
  if len(functions) > 0 {
1257
1517
  analysis.FunctionDefs[baseFileName] = functions
1258
1518
  }
1259
- if len(types) > 0 {
1260
- analysis.TypeDefs[baseFileName] = types
1519
+ if len(typeNames) > 0 {
1520
+ analysis.TypeDefs[baseFileName] = typeNames
1521
+ }
1522
+ if len(variables) > 0 {
1523
+ analysis.VariableDefs[baseFileName] = variables
1261
1524
  }
1262
1525
  }
1263
1526
 
@@ -1276,13 +1539,7 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1276
1539
 
1277
1540
  // Check if this function is defined in the current file
1278
1541
  currentFileFuncs := analysis.FunctionDefs[baseFileName]
1279
- isDefinedInCurrentFile := false
1280
- for _, f := range currentFileFuncs {
1281
- if f == funcName {
1282
- isDefinedInCurrentFile = true
1283
- break
1284
- }
1285
- }
1542
+ isDefinedInCurrentFile := slices.Contains(currentFileFuncs, funcName)
1286
1543
 
1287
1544
  // If not defined in current file, find which file defines it
1288
1545
  if !isDefinedInCurrentFile {
@@ -1290,24 +1547,15 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1290
1547
  if sourceFile == baseFileName {
1291
1548
  continue // Skip current file
1292
1549
  }
1293
- for _, f := range funcs {
1294
- if f == funcName {
1295
- // Found the function in another file
1296
- if callsFromOtherFiles[sourceFile] == nil {
1297
- callsFromOtherFiles[sourceFile] = []string{}
1298
- }
1299
- // Check if already added to avoid duplicates
1300
- found := false
1301
- for _, existing := range callsFromOtherFiles[sourceFile] {
1302
- if existing == funcName {
1303
- found = true
1304
- break
1305
- }
1306
- }
1307
- if !found {
1308
- callsFromOtherFiles[sourceFile] = append(callsFromOtherFiles[sourceFile], funcName)
1309
- }
1310
- break
1550
+ if slices.Contains(funcs, funcName) {
1551
+ // Found the function in another file
1552
+ if callsFromOtherFiles[sourceFile] == nil {
1553
+ callsFromOtherFiles[sourceFile] = []string{}
1554
+ }
1555
+ // Check if already added to avoid duplicates
1556
+ found := slices.Contains(callsFromOtherFiles[sourceFile], funcName)
1557
+ if !found {
1558
+ callsFromOtherFiles[sourceFile] = append(callsFromOtherFiles[sourceFile], funcName)
1311
1559
  }
1312
1560
  }
1313
1561
  }
@@ -1340,13 +1588,7 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1340
1588
 
1341
1589
  // Check if this type is defined in the current file
1342
1590
  currentFileTypes := analysis.TypeDefs[baseFileName]
1343
- isDefinedInCurrentFile := false
1344
- for _, t := range currentFileTypes {
1345
- if t == typeName {
1346
- isDefinedInCurrentFile = true
1347
- break
1348
- }
1349
- }
1591
+ isDefinedInCurrentFile := slices.Contains(currentFileTypes, typeName)
1350
1592
 
1351
1593
  // If not defined in current file, find which file defines it
1352
1594
  if !isDefinedInCurrentFile {
@@ -1354,24 +1596,99 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1354
1596
  if sourceFile == baseFileName {
1355
1597
  continue // Skip current file
1356
1598
  }
1357
- for _, t := range types {
1358
- if t == typeName {
1359
- // Found the type in another file
1360
- if typeRefsFromOtherFiles[sourceFile] == nil {
1361
- typeRefsFromOtherFiles[sourceFile] = []string{}
1599
+ if slices.Contains(types, typeName) {
1600
+ // Found the type in another file
1601
+ if typeRefsFromOtherFiles[sourceFile] == nil {
1602
+ typeRefsFromOtherFiles[sourceFile] = []string{}
1603
+ }
1604
+ // Check if already added to avoid duplicates
1605
+ found := slices.Contains(typeRefsFromOtherFiles[sourceFile], typeName)
1606
+ if !found {
1607
+ typeRefsFromOtherFiles[sourceFile] = append(typeRefsFromOtherFiles[sourceFile], typeName)
1608
+ }
1609
+ }
1610
+ }
1611
+ }
1612
+ }
1613
+ }
1614
+ }
1615
+ return true
1616
+ })
1617
+
1618
+ if len(typeRefsFromOtherFiles) > 0 {
1619
+ analysis.TypeCalls[baseFileName] = typeRefsFromOtherFiles
1620
+ }
1621
+ }
1622
+
1623
+ // Fourth pass: analyze variable references and determine which need imports
1624
+ for i, syntax := range pkg.Syntax {
1625
+ fileName := pkg.CompiledGoFiles[i]
1626
+ baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
1627
+
1628
+ // Find all variable references in this file
1629
+ varRefsFromOtherFiles := make(map[string][]string)
1630
+
1631
+ ast.Inspect(syntax, func(n ast.Node) bool {
1632
+ // Look for identifier references
1633
+ if ident, ok := n.(*ast.Ident); ok {
1634
+ // Check if this identifier refers to a package-level variable
1635
+ if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
1636
+ if varObj, ok := obj.(*types.Var); ok {
1637
+ // Only track package-level variables (not function parameters or local vars)
1638
+ if varObj.Parent() == pkg.Types.Scope() {
1639
+ varName := ident.Name
1640
+
1641
+ // Check if this variable is defined in the current file
1642
+ currentFileVars := analysis.VariableDefs[baseFileName]
1643
+ isDefinedInCurrentFile := slices.Contains(currentFileVars, varName)
1644
+
1645
+ // If not defined in current file, find which file defines it
1646
+ if !isDefinedInCurrentFile {
1647
+ for sourceFile, vars := range analysis.VariableDefs {
1648
+ if sourceFile == baseFileName {
1649
+ continue // Skip current file
1650
+ }
1651
+ if slices.Contains(vars, varName) {
1652
+ // Found the variable in another file
1653
+ if varRefsFromOtherFiles[sourceFile] == nil {
1654
+ varRefsFromOtherFiles[sourceFile] = []string{}
1362
1655
  }
1363
1656
  // Check if already added to avoid duplicates
1364
- found := false
1365
- for _, existing := range typeRefsFromOtherFiles[sourceFile] {
1366
- if existing == typeName {
1367
- found = true
1368
- break
1369
- }
1657
+ found := slices.Contains(varRefsFromOtherFiles[sourceFile], varName)
1658
+ if !found {
1659
+ varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], varName)
1660
+ }
1661
+ }
1662
+ }
1663
+ }
1664
+ }
1665
+ }
1666
+ // Also check for constants
1667
+ if constObj, ok := obj.(*types.Const); ok {
1668
+ // Only track package-level constants
1669
+ if constObj.Parent() == pkg.Types.Scope() {
1670
+ constName := ident.Name
1671
+
1672
+ // Check if this constant is defined in the current file
1673
+ currentFileVars := analysis.VariableDefs[baseFileName]
1674
+ isDefinedInCurrentFile := slices.Contains(currentFileVars, constName)
1675
+
1676
+ // If not defined in current file, find which file defines it
1677
+ if !isDefinedInCurrentFile {
1678
+ for sourceFile, vars := range analysis.VariableDefs {
1679
+ if sourceFile == baseFileName {
1680
+ continue // Skip current file
1681
+ }
1682
+ if slices.Contains(vars, constName) {
1683
+ // Found the constant in another file
1684
+ if varRefsFromOtherFiles[sourceFile] == nil {
1685
+ varRefsFromOtherFiles[sourceFile] = []string{}
1370
1686
  }
1687
+ // Check if already added to avoid duplicates
1688
+ found := slices.Contains(varRefsFromOtherFiles[sourceFile], constName)
1371
1689
  if !found {
1372
- typeRefsFromOtherFiles[sourceFile] = append(typeRefsFromOtherFiles[sourceFile], typeName)
1690
+ varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], constName)
1373
1691
  }
1374
- break
1375
1692
  }
1376
1693
  }
1377
1694
  }
@@ -1382,11 +1699,93 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1382
1699
  return true
1383
1700
  })
1384
1701
 
1385
- if len(typeRefsFromOtherFiles) > 0 {
1386
- analysis.TypeCalls[baseFileName] = typeRefsFromOtherFiles
1702
+ if len(varRefsFromOtherFiles) > 0 {
1703
+ analysis.VariableCalls[baseFileName] = varRefsFromOtherFiles
1387
1704
  }
1388
1705
  }
1389
1706
 
1707
+ // Fifth pass: analyze method calls on wrapper types (named basic types with methods)
1708
+ // These generate TypeName_MethodName function calls that need to be imported
1709
+ for i, syntax := range pkg.Syntax {
1710
+ fileName := pkg.CompiledGoFiles[i]
1711
+ baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
1712
+
1713
+ // Find all method calls on wrapper types in this file
1714
+ ast.Inspect(syntax, func(n ast.Node) bool {
1715
+ callExpr, ok := n.(*ast.CallExpr)
1716
+ if !ok {
1717
+ return true
1718
+ }
1719
+
1720
+ // Check if this is a method call (selector expression)
1721
+ selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
1722
+ if !ok {
1723
+ return true
1724
+ }
1725
+
1726
+ // Get the type of the receiver
1727
+ receiverType := pkg.TypesInfo.TypeOf(selectorExpr.X)
1728
+ if receiverType == nil {
1729
+ return true
1730
+ }
1731
+
1732
+ // Check if this is a wrapper type (named type with basic underlying type and methods)
1733
+ namedType, ok := receiverType.(*types.Named)
1734
+ if !ok {
1735
+ return true
1736
+ }
1737
+
1738
+ // Check if it has a basic underlying type
1739
+ if _, ok := namedType.Underlying().(*types.Basic); !ok {
1740
+ return true
1741
+ }
1742
+
1743
+ // Check if this type is defined in the same package
1744
+ obj := namedType.Obj()
1745
+ if obj == nil || obj.Pkg() == nil || obj.Pkg() != pkg.Types {
1746
+ return true // Not from this package
1747
+ }
1748
+
1749
+ // Check if this type has the method being called
1750
+ methodName := selectorExpr.Sel.Name
1751
+ found := false
1752
+ for j := 0; j < namedType.NumMethods(); j++ {
1753
+ if namedType.Method(j).Name() == methodName {
1754
+ found = true
1755
+ break
1756
+ }
1757
+ }
1758
+ if !found {
1759
+ return true
1760
+ }
1761
+
1762
+ // Generate the function name: TypeName_MethodName
1763
+ funcName := obj.Name() + "_" + methodName
1764
+
1765
+ // Find which file defines this function
1766
+ for sourceFile, funcs := range analysis.FunctionDefs {
1767
+ if sourceFile == baseFileName {
1768
+ continue // Skip current file
1769
+ }
1770
+ if slices.Contains(funcs, funcName) {
1771
+ // Found the function in another file
1772
+ if analysis.FunctionCalls[baseFileName] == nil {
1773
+ analysis.FunctionCalls[baseFileName] = make(map[string][]string)
1774
+ }
1775
+ if analysis.FunctionCalls[baseFileName][sourceFile] == nil {
1776
+ analysis.FunctionCalls[baseFileName][sourceFile] = []string{}
1777
+ }
1778
+ // Check if already added to avoid duplicates
1779
+ if !slices.Contains(analysis.FunctionCalls[baseFileName][sourceFile], funcName) {
1780
+ analysis.FunctionCalls[baseFileName][sourceFile] = append(analysis.FunctionCalls[baseFileName][sourceFile], funcName)
1781
+ }
1782
+ }
1783
+ }
1784
+
1785
+ return true
1786
+ })
1787
+ }
1788
+
1390
1789
  return analysis
1391
1790
  }
1392
1791
 
@@ -1590,6 +1989,7 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
1590
1989
  shadowingInfo := &ShadowingInfo{
1591
1990
  ShadowedVariables: make(map[string]types.Object),
1592
1991
  TempVariables: make(map[string]string),
1992
+ TypeShadowedVars: make(map[string]string),
1593
1993
  }
1594
1994
 
1595
1995
  hasShadowing := false
@@ -1607,12 +2007,60 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
1607
2007
  v.findVariableUsageInExpr(rhsExpr, lhsVarNames, shadowingInfo, &hasShadowing)
1608
2008
  }
1609
2009
 
2010
+ // Check for type shadowing: variable name matches a type name used in its initialization
2011
+ // e.g., field := field{...} where the variable 'field' shadows the type 'field'
2012
+ if assignStmt.Tok == token.DEFINE {
2013
+ for i, lhsExpr := range assignStmt.Lhs {
2014
+ if i < len(assignStmt.Rhs) {
2015
+ if lhsIdent, ok := lhsExpr.(*ast.Ident); ok && lhsIdent.Name != "_" {
2016
+ if typeName := v.findTypeShadowing(lhsIdent.Name, assignStmt.Rhs[i]); typeName != "" {
2017
+ shadowingInfo.TypeShadowedVars[lhsIdent.Name] = lhsIdent.Name + "_"
2018
+ hasShadowing = true
2019
+ }
2020
+ }
2021
+ }
2022
+ }
2023
+ }
2024
+
1610
2025
  if hasShadowing {
1611
2026
  return shadowingInfo
1612
2027
  }
1613
2028
  return nil
1614
2029
  }
1615
2030
 
2031
+ // findTypeShadowing checks if the given variable name matches a type name used in the RHS expression.
2032
+ // Returns the type name if shadowing is detected, empty string otherwise.
2033
+ func (v *analysisVisitor) findTypeShadowing(varName string, rhsExpr ast.Expr) string {
2034
+ // Handle address-of expressions: field := &field{...}
2035
+ if unary, ok := rhsExpr.(*ast.UnaryExpr); ok && unary.Op == token.AND {
2036
+ rhsExpr = unary.X
2037
+ }
2038
+
2039
+ // Check if RHS is a composite literal with a type name matching varName
2040
+ compLit, ok := rhsExpr.(*ast.CompositeLit)
2041
+ if !ok {
2042
+ return ""
2043
+ }
2044
+
2045
+ // Get the type name from the composite literal
2046
+ var typeName string
2047
+ switch t := compLit.Type.(type) {
2048
+ case *ast.Ident:
2049
+ typeName = t.Name
2050
+ case *ast.SelectorExpr:
2051
+ // pkg.Type - just use the type name part
2052
+ typeName = t.Sel.Name
2053
+ default:
2054
+ return ""
2055
+ }
2056
+
2057
+ // Check if variable name matches type name
2058
+ if typeName == varName {
2059
+ return typeName
2060
+ }
2061
+ return ""
2062
+ }
2063
+
1616
2064
  // findVariableUsageInExpr recursively searches for variable usage in an expression
1617
2065
  func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map[string]*ast.Ident, shadowingInfo *ShadowingInfo, hasShadowing *bool) {
1618
2066
  if expr == nil {
@@ -1695,16 +2143,15 @@ func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map
1695
2143
  }
1696
2144
 
1697
2145
  // trackInterfaceImplementation records that a struct type implements an interface method
1698
- func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func, isAsync bool) {
2146
+ func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func) {
1699
2147
  key := InterfaceMethodKey{
1700
2148
  InterfaceType: interfaceType.String(),
1701
2149
  MethodName: method.Name(),
1702
2150
  }
1703
2151
 
1704
2152
  implementation := ImplementationInfo{
1705
- StructType: structType,
1706
- Method: method,
1707
- IsAsyncByFlow: isAsync,
2153
+ StructType: structType,
2154
+ Method: method,
1708
2155
  }
1709
2156
 
1710
2157
  a.InterfaceImplementations[key] = append(a.InterfaceImplementations[key], implementation)
@@ -1717,49 +2164,25 @@ func (a *Analysis) IsInterfaceMethodAsync(interfaceType *types.Interface, method
1717
2164
  MethodName: methodName,
1718
2165
  }
1719
2166
 
1720
- // Check if we've already computed this
1721
- if result, exists := a.InterfaceMethodAsyncStatus[key]; exists {
1722
- return result
1723
- }
1724
-
1725
2167
  // Find all implementations of this interface method
1726
2168
  implementations, exists := a.InterfaceImplementations[key]
1727
2169
  if !exists {
1728
- // No implementations found, default to sync
1729
- a.InterfaceMethodAsyncStatus[key] = false
1730
2170
  return false
1731
2171
  }
1732
2172
 
1733
- // Update implementations with current async status before checking
1734
- for i := range implementations {
1735
- impl := &implementations[i]
1736
-
1737
- // Create method key for this implementation
2173
+ // If ANY implementation is async, the interface method is async
2174
+ for _, impl := range implementations {
1738
2175
  methodKey := MethodKey{
1739
2176
  PackagePath: impl.StructType.Obj().Pkg().Path(),
1740
2177
  ReceiverType: impl.StructType.Obj().Name(),
1741
2178
  MethodName: impl.Method.Name(),
1742
2179
  }
1743
2180
 
1744
- // Update with current async status from method analysis
1745
- if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists {
1746
- impl.IsAsyncByFlow = isAsync
1747
- }
1748
- }
1749
-
1750
- // Store the updated implementations back to the map
1751
- a.InterfaceImplementations[key] = implementations
1752
-
1753
- // If ANY implementation is async, the interface method is async
1754
- for _, impl := range implementations {
1755
- if impl.IsAsyncByFlow {
1756
- a.InterfaceMethodAsyncStatus[key] = true
2181
+ if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists && isAsync {
1757
2182
  return true
1758
2183
  }
1759
2184
  }
1760
2185
 
1761
- // All implementations are sync
1762
- a.InterfaceMethodAsyncStatus[key] = false
1763
2186
  return false
1764
2187
  }
1765
2188
 
@@ -1844,56 +2267,6 @@ func (a *Analysis) GetIdentifierMapping(ident *ast.Ident) string {
1844
2267
  return ""
1845
2268
  }
1846
2269
 
1847
- // trackTypeAssertion analyzes type assertions and records interface implementations
1848
- func (v *analysisVisitor) trackTypeAssertion(typeAssert *ast.TypeAssertExpr) {
1849
- // Get the type being asserted to
1850
- assertedType := v.pkg.TypesInfo.TypeOf(typeAssert.Type)
1851
- if assertedType == nil {
1852
- return
1853
- }
1854
-
1855
- // Check if the asserted type is an interface
1856
- interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
1857
- if !isInterface {
1858
- return
1859
- }
1860
-
1861
- // Get the type of the expression being asserted
1862
- exprType := v.pkg.TypesInfo.TypeOf(typeAssert.X)
1863
- if exprType == nil {
1864
- return
1865
- }
1866
-
1867
- // Handle pointer types by getting the element type
1868
- if ptrType, isPtr := exprType.(*types.Pointer); isPtr {
1869
- exprType = ptrType.Elem()
1870
- }
1871
-
1872
- // Check if the expression type is a named struct type
1873
- namedType, isNamed := exprType.(*types.Named)
1874
- if !isNamed {
1875
- return
1876
- }
1877
-
1878
- // For each method in the interface, check if the struct implements it
1879
- for i := 0; i < interfaceType.NumExplicitMethods(); i++ {
1880
- interfaceMethod := interfaceType.ExplicitMethod(i)
1881
-
1882
- // Find the corresponding method in the struct type
1883
- structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
1884
- if structMethod != nil {
1885
- // Determine if this struct method is async using unified system
1886
- isAsync := false
1887
- if obj := structMethod; obj != nil {
1888
- isAsync = v.analysis.IsAsyncFunc(obj)
1889
- }
1890
-
1891
- // Track this interface implementation
1892
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
1893
- }
1894
- }
1895
- }
1896
-
1897
2270
  // findStructMethod finds a method with the given name on a named type
1898
2271
  func (v *analysisVisitor) findStructMethod(namedType *types.Named, methodName string) *types.Func {
1899
2272
  // Check methods directly on the type
@@ -2006,10 +2379,7 @@ func (v *analysisVisitor) trackInterfaceAssignments(assignStmt *ast.AssignStmt)
2006
2379
 
2007
2380
  structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
2008
2381
  if structMethod != nil {
2009
- // Determine if this struct method is async using unified system
2010
- isAsync := v.analysis.IsAsyncFunc(structMethod)
2011
-
2012
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
2382
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
2013
2383
  }
2014
2384
  }
2015
2385
  }
@@ -2068,9 +2438,7 @@ func (v *analysisVisitor) trackInterfaceCallArguments(callExpr *ast.CallExpr) {
2068
2438
 
2069
2439
  structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
2070
2440
  if structMethod != nil {
2071
- // Note: Don't determine async status here - it will be determined later after method analysis
2072
- // For now, just track the implementation relationship without async status
2073
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, false)
2441
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
2074
2442
  }
2075
2443
  }
2076
2444
  }
@@ -2216,10 +2584,7 @@ func (v *interfaceImplementationVisitor) trackImplementation(interfaceType *type
2216
2584
  // Find the method in the implementing type
2217
2585
  structMethod := v.findMethodInType(namedType, interfaceMethod.Name())
2218
2586
  if structMethod != nil {
2219
- // Determine if this implementation is async using unified system
2220
- isAsync := v.analysis.IsAsyncFunc(structMethod)
2221
-
2222
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
2587
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
2223
2588
  }
2224
2589
  }
2225
2590
  }
@@ -2235,93 +2600,106 @@ func (v *interfaceImplementationVisitor) findMethodInType(namedType *types.Named
2235
2600
  return nil
2236
2601
  }
2237
2602
 
2238
- // getNamedReturns retrieves the named returns for a function
2239
- func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
2240
- var namedReturns []string
2241
- if funcDecl.Type != nil && funcDecl.Type.Results != nil {
2242
- for _, field := range funcDecl.Type.Results.List {
2243
- for _, name := range field.Names {
2244
- namedReturns = append(namedReturns, name.Name)
2245
- }
2246
- }
2247
- }
2248
- return namedReturns
2249
- }
2250
-
2251
- // analyzeAllMethodsAsync performs comprehensive async analysis on all methods in all packages
2603
+ // analyzeAllMethodsAsync performs comprehensive async analysis on all methods in all packages using topological sort
2252
2604
  func (v *analysisVisitor) analyzeAllMethodsAsync() {
2253
- // Initialize visitingMethods map
2254
- v.visitingMethods = make(map[MethodKey]bool)
2255
-
2256
- // Fixed-point iteration: keep analyzing until nothing changes
2257
- // This handles cases where method A calls method B, but B is analyzed after A
2605
+ // Build the method call graph for all packages
2606
+ methodCalls := v.buildMethodCallGraph()
2607
+
2608
+ // Topologically sort methods by their dependencies
2609
+ sorted, cycles := v.topologicalSortMethods(methodCalls)
2610
+
2611
+ // Mark methods in cycles - check if they contain async operations
2612
+ // We can't rely on the call graph for methods in cycles (circular dependency),
2613
+ // but we can still detect if they call async external methods
2614
+ //
2615
+ // We need to iterate multiple times because methods in cycles can call each other,
2616
+ // and we need to propagate async status until no changes occur
2258
2617
  maxIterations := 10
2259
2618
  for iteration := 0; iteration < maxIterations; iteration++ {
2260
- // Clear visitingMethods map for this iteration
2261
- v.visitingMethods = make(map[MethodKey]bool)
2262
-
2263
- // Track if anything changed in this iteration
2264
2619
  changed := false
2265
2620
 
2266
- // Save previous state
2267
- previousState := make(map[MethodKey]bool)
2268
- for k, v := range v.analysis.MethodAsyncStatus {
2269
- previousState[k] = v
2270
- }
2621
+ for _, methodKey := range cycles {
2622
+ // For methods in cycles, we need to check their body directly for async operations
2623
+ pkg := v.analysis.AllPackages[methodKey.PackagePath]
2624
+ if pkg == nil && methodKey.PackagePath == v.pkg.Types.Path() {
2625
+ pkg = v.pkg
2626
+ }
2271
2627
 
2272
- // Re-analyze methods in current package
2273
- v.analyzePackageMethodsAsync(v.pkg)
2628
+ if pkg != nil {
2629
+ var funcDecl *ast.FuncDecl
2630
+ if methodKey.ReceiverType == "" {
2631
+ funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
2632
+ } else {
2633
+ funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
2634
+ }
2274
2635
 
2275
- // Re-analyze methods in all dependency packages
2276
- for _, pkg := range v.analysis.AllPackages {
2277
- if pkg != v.pkg {
2278
- v.analyzePackageMethodsAsync(pkg)
2279
- }
2280
- }
2636
+ if funcDecl != nil && funcDecl.Body != nil {
2637
+ // Check if the method contains async operations (including calls to async external methods)
2638
+ isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2639
+
2640
+ // Check if status changed
2641
+ if oldStatus, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists || oldStatus != isAsync {
2642
+ changed = true
2643
+ }
2281
2644
 
2282
- // Check if anything changed
2283
- for k, newValue := range v.analysis.MethodAsyncStatus {
2284
- oldValue, existed := previousState[k]
2285
- if !existed {
2286
- // New method added - check if it's async (if sync, no need to re-analyze dependents)
2287
- if newValue {
2288
- changed = true
2645
+ v.analysis.MethodAsyncStatus[methodKey] = isAsync
2646
+ continue
2289
2647
  }
2290
- } else if oldValue != newValue {
2291
- // Method changed from sync to async (or vice versa)
2648
+ }
2649
+
2650
+ // Fallback: mark as sync if we can't analyze the body
2651
+ if _, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists {
2652
+ v.analysis.MethodAsyncStatus[methodKey] = false
2292
2653
  changed = true
2293
2654
  }
2294
2655
  }
2295
2656
 
2296
- // If nothing changed, we've reached a fixed point
2657
+ // If no changes in this iteration, we're done
2297
2658
  if !changed {
2298
2659
  break
2299
2660
  }
2300
2661
  }
2301
2662
 
2663
+ // Analyze methods in dependency order (dependencies analyzed before dependents)
2664
+ for _, methodKey := range sorted {
2665
+ v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
2666
+ }
2667
+
2668
+ // Track async-returning variables BEFORE analyzing function literals
2669
+ // This detects variables assigned from higher-order functions with async function literal args
2670
+ // e.g., indirect := sync.OnceValue(asyncFunc)
2671
+ // This must happen first so that function literals containing calls to these variables
2672
+ // will be correctly identified as async.
2673
+ v.trackAsyncReturningVarsAllFiles()
2674
+
2302
2675
  // Finally, analyze function literals in the current package only
2303
2676
  // (external packages' function literals are not accessible)
2677
+ // This must run AFTER trackAsyncReturningVarsAllFiles so that function literals
2678
+ // containing calls to async-returning variables are correctly marked as async.
2304
2679
  v.analyzeFunctionLiteralsAsync(v.pkg)
2305
2680
  }
2306
2681
 
2307
- // analyzePackageMethodsAsync analyzes all methods in a specific package
2308
- func (v *analysisVisitor) analyzePackageMethodsAsync(pkg *packages.Package) {
2309
- // Analyze function declarations
2682
+ // buildMethodCallGraph builds a graph of which methods call which other methods
2683
+ func (v *analysisVisitor) analyzeFunctionLiteralsAsync(pkg *packages.Package) {
2310
2684
  for _, file := range pkg.Syntax {
2311
- for _, decl := range file.Decls {
2312
- if funcDecl, ok := decl.(*ast.FuncDecl); ok {
2313
- v.analyzeMethodAsync(funcDecl, pkg)
2685
+ ast.Inspect(file, func(n ast.Node) bool {
2686
+ if funcLit, ok := n.(*ast.FuncLit); ok {
2687
+ v.analyzeFunctionLiteralAsync(funcLit, pkg)
2314
2688
  }
2315
- }
2689
+ return true
2690
+ })
2316
2691
  }
2317
2692
  }
2318
2693
 
2319
- // analyzeFunctionLiteralsAsync analyzes all function literals in a package for async operations
2320
- func (v *analysisVisitor) analyzeFunctionLiteralsAsync(pkg *packages.Package) {
2321
- for _, file := range pkg.Syntax {
2694
+ // trackAsyncReturningVarsAllFiles scans all files for assignment statements
2695
+ // and marks variables that are assigned from higher-order functions with async function literal args
2696
+ func (v *analysisVisitor) trackAsyncReturningVarsAllFiles() {
2697
+ for _, file := range v.pkg.Syntax {
2322
2698
  ast.Inspect(file, func(n ast.Node) bool {
2323
- if funcLit, ok := n.(*ast.FuncLit); ok {
2324
- v.analyzeFunctionLiteralAsync(funcLit, pkg)
2699
+ if assignStmt, ok := n.(*ast.AssignStmt); ok {
2700
+ if len(assignStmt.Lhs) == 1 && len(assignStmt.Rhs) == 1 {
2701
+ v.trackAsyncReturningVar(assignStmt.Lhs[0], assignStmt.Rhs[0])
2702
+ }
2325
2703
  }
2326
2704
  return true
2327
2705
  })
@@ -2350,55 +2728,320 @@ func (v *analysisVisitor) analyzeFunctionLiteralAsync(funcLit *ast.FuncLit, pkg
2350
2728
  nodeInfo.InAsyncContext = isAsync
2351
2729
  }
2352
2730
 
2353
- // analyzeMethodAsync determines if a method is async and stores the result
2354
- func (v *analysisVisitor) analyzeMethodAsync(funcDecl *ast.FuncDecl, pkg *packages.Package) {
2355
- methodKey := v.getMethodKey(funcDecl, pkg)
2731
+ // buildMethodCallGraph builds a graph of which methods call which other methods
2732
+ func (v *analysisVisitor) buildMethodCallGraph() map[MethodKey][]MethodKey {
2733
+ methodCalls := make(map[MethodKey][]MethodKey)
2356
2734
 
2357
- // Check for cycles
2358
- if v.visitingMethods[methodKey] {
2359
- // Cycle detected, assume sync to break recursion
2360
- v.analysis.MethodAsyncStatus[methodKey] = false
2361
- return
2735
+ // Iterate through all packages
2736
+ allPkgs := []*packages.Package{v.pkg}
2737
+ for _, pkg := range v.analysis.AllPackages {
2738
+ if pkg != v.pkg {
2739
+ allPkgs = append(allPkgs, pkg)
2740
+ }
2362
2741
  }
2363
2742
 
2364
- // Mark as visiting
2365
- v.visitingMethods[methodKey] = true
2743
+ for _, pkg := range allPkgs {
2744
+ for _, file := range pkg.Syntax {
2745
+ for _, decl := range file.Decls {
2746
+ if funcDecl, ok := decl.(*ast.FuncDecl); ok {
2747
+ methodKey := v.getMethodKey(funcDecl, pkg)
2366
2748
 
2367
- // Determine if method is async
2368
- isAsync := false
2749
+ // Initialize the entry for this method
2750
+ if _, exists := methodCalls[methodKey]; !exists {
2751
+ methodCalls[methodKey] = []MethodKey{}
2752
+ }
2753
+
2754
+ // Extract method calls from the function body
2755
+ if funcDecl.Body != nil {
2756
+ callees := v.extractMethodCalls(funcDecl.Body, pkg)
2757
+ methodCalls[methodKey] = callees
2758
+ }
2759
+ }
2760
+ }
2761
+ }
2762
+ }
2763
+
2764
+ return methodCalls
2765
+ }
2766
+
2767
+ // extractMethodCalls extracts all method and function calls from a node
2768
+ func (v *analysisVisitor) extractMethodCalls(node ast.Node, pkg *packages.Package) []MethodKey {
2769
+ var calls []MethodKey
2770
+ seen := make(map[MethodKey]bool)
2771
+
2772
+ ast.Inspect(node, func(n ast.Node) bool {
2773
+ if n == nil {
2774
+ return false
2775
+ }
2776
+
2777
+ if callExpr, ok := n.(*ast.CallExpr); ok {
2778
+ methodKeys := v.extractMethodKeysFromCall(callExpr, pkg)
2779
+ for _, methodKey := range methodKeys {
2780
+ if !seen[methodKey] {
2781
+ seen[methodKey] = true
2782
+ calls = append(calls, methodKey)
2783
+ }
2784
+ }
2785
+ }
2786
+
2787
+ return true
2788
+ })
2789
+
2790
+ return calls
2791
+ }
2792
+
2793
+ // extractMethodKeysFromCall extracts MethodKeys from a call expression
2794
+ // Returns multiple keys for interface method calls (one for each implementation)
2795
+ func (v *analysisVisitor) extractMethodKeysFromCall(callExpr *ast.CallExpr, pkg *packages.Package) []MethodKey {
2796
+ singleKey := v.extractMethodKeyFromCall(callExpr, pkg)
2797
+ if singleKey != nil {
2798
+ return []MethodKey{*singleKey}
2799
+ }
2800
+
2801
+ // Check if this is an interface method call
2802
+ if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
2803
+ if receiverType := pkg.TypesInfo.TypeOf(selExpr.X); receiverType != nil {
2804
+ if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
2805
+ methodName := selExpr.Sel.Name
2806
+ // Find all implementations of this interface method
2807
+ key := InterfaceMethodKey{
2808
+ InterfaceType: interfaceType.String(),
2809
+ MethodName: methodName,
2810
+ }
2811
+ if implementations, exists := v.analysis.InterfaceImplementations[key]; exists {
2812
+ var keys []MethodKey
2813
+ for _, impl := range implementations {
2814
+ keys = append(keys, MethodKey{
2815
+ PackagePath: impl.StructType.Obj().Pkg().Path(),
2816
+ ReceiverType: impl.StructType.Obj().Name(),
2817
+ MethodName: impl.Method.Name(),
2818
+ })
2819
+ }
2820
+ return keys
2821
+ }
2822
+ }
2823
+ }
2824
+ }
2825
+
2826
+ return nil
2827
+ }
2828
+
2829
+ // extractMethodKeyFromCall extracts a MethodKey from a call expression
2830
+ func (v *analysisVisitor) extractMethodKeyFromCall(callExpr *ast.CallExpr, pkg *packages.Package) *MethodKey {
2831
+ switch fun := callExpr.Fun.(type) {
2832
+ case *ast.Ident:
2833
+ // Direct function call
2834
+ if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
2835
+ if funcObj, ok := obj.(*types.Func); ok {
2836
+ pkgPath := pkg.Types.Path()
2837
+ if funcObj.Pkg() != nil {
2838
+ pkgPath = funcObj.Pkg().Path()
2839
+ }
2840
+ return &MethodKey{
2841
+ PackagePath: pkgPath,
2842
+ ReceiverType: "",
2843
+ MethodName: funcObj.Name(),
2844
+ }
2845
+ }
2846
+ }
2847
+
2848
+ case *ast.SelectorExpr:
2849
+ // Package-level function call (e.g., time.Sleep)
2850
+ if ident, ok := fun.X.(*ast.Ident); ok {
2851
+ if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
2852
+ if pkgName, isPkg := obj.(*types.PkgName); isPkg {
2853
+ return &MethodKey{
2854
+ PackagePath: pkgName.Imported().Path(),
2855
+ ReceiverType: "",
2856
+ MethodName: fun.Sel.Name,
2857
+ }
2858
+ }
2859
+ }
2860
+ }
2861
+
2862
+ // Check if this is an interface method call - if so, return nil
2863
+ // so extractMethodKeysFromCall can expand it to all implementations
2864
+ if receiverType := pkg.TypesInfo.TypeOf(fun.X); receiverType != nil {
2865
+ if _, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
2866
+ // This is an interface method call - return nil to let
2867
+ // extractMethodKeysFromCall handle expanding to implementations
2868
+ return nil
2869
+ }
2870
+ }
2871
+
2872
+ // Method call on concrete objects
2873
+ if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
2874
+ if methodObj := selection.Obj(); methodObj != nil {
2875
+ receiverType := ""
2876
+ methodPkgPath := ""
2877
+
2878
+ // Get receiver type
2879
+ switch x := fun.X.(type) {
2880
+ case *ast.Ident:
2881
+ if obj := pkg.TypesInfo.Uses[x]; obj != nil {
2882
+ if varObj, ok := obj.(*types.Var); ok {
2883
+ receiverType = v.getTypeName(varObj.Type())
2884
+ }
2885
+ }
2886
+ case *ast.SelectorExpr:
2887
+ if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
2888
+ receiverType = v.getTypeName(typeExpr)
2889
+ }
2890
+ }
2891
+
2892
+ // Get method's package path
2893
+ if methodFunc, ok := methodObj.(*types.Func); ok {
2894
+ if methodFunc.Pkg() != nil {
2895
+ methodPkgPath = methodFunc.Pkg().Path()
2896
+ }
2897
+ }
2898
+
2899
+ if methodPkgPath == "" {
2900
+ methodPkgPath = pkg.Types.Path()
2901
+ }
2902
+
2903
+ return &MethodKey{
2904
+ PackagePath: methodPkgPath,
2905
+ ReceiverType: receiverType,
2906
+ MethodName: methodObj.Name(),
2907
+ }
2908
+ }
2909
+ }
2910
+ }
2369
2911
 
2370
- // Determine if this is a handwritten package (from gs/ directory)
2371
- // Handwritten packages should not have their bodies analyzed
2912
+ return nil
2913
+ }
2914
+
2915
+ // topologicalSortMethods performs a topological sort of methods based on their call dependencies
2916
+ // Returns sorted methods and methods involved in cycles
2917
+ func (v *analysisVisitor) topologicalSortMethods(methodCalls map[MethodKey][]MethodKey) ([]MethodKey, []MethodKey) {
2918
+ // Kahn's algorithm for topological sorting
2919
+ inDegree := make(map[MethodKey]int)
2920
+ graph := make(map[MethodKey][]MethodKey)
2921
+
2922
+ // Initialize in-degree counts and reverse graph
2923
+ for method := range methodCalls {
2924
+ inDegree[method] = 0
2925
+ graph[method] = []MethodKey{}
2926
+ }
2927
+
2928
+ // Build reverse graph and count in-degrees
2929
+ // graph[dependency] = methods that depend on it
2930
+ for method, callees := range methodCalls {
2931
+ for _, callee := range callees {
2932
+ // Only create dependency edges for callees that exist in the call graph
2933
+ // This automatically excludes handwritten packages that aren't being compiled
2934
+ if _, exists := inDegree[callee]; exists {
2935
+ // Additionally, skip if the callee is from a handwritten package
2936
+ // This prevents our code from being blocked by unresolved handwritten package dependencies
2937
+ if !v.analysis.isHandwrittenPackage(callee.PackagePath) {
2938
+ graph[callee] = append(graph[callee], method)
2939
+ inDegree[method]++
2940
+ }
2941
+ }
2942
+ }
2943
+ }
2944
+
2945
+ // Find methods with no dependencies (in-degree == 0)
2946
+ var queue []MethodKey
2947
+ for method, degree := range inDegree {
2948
+ if degree == 0 {
2949
+ queue = append(queue, method)
2950
+ }
2951
+ }
2952
+
2953
+ var sorted []MethodKey
2954
+
2955
+ for len(queue) > 0 {
2956
+ // Remove method from queue
2957
+ current := queue[0]
2958
+ queue = queue[1:]
2959
+ sorted = append(sorted, current)
2960
+
2961
+ // For each method that depends on current
2962
+ for _, dependent := range graph[current] {
2963
+ inDegree[dependent]--
2964
+ if inDegree[dependent] == 0 {
2965
+ queue = append(queue, dependent)
2966
+ }
2967
+ }
2968
+ }
2969
+
2970
+ // Find methods in cycles (not in sorted list)
2971
+ var cycles []MethodKey
2972
+ if len(sorted) != len(methodCalls) {
2973
+ for method := range methodCalls {
2974
+ found := slices.Contains(sorted, method)
2975
+ if !found {
2976
+ cycles = append(cycles, method)
2977
+ }
2978
+ }
2979
+ }
2980
+
2981
+ return sorted, cycles
2982
+ }
2983
+
2984
+ // analyzeMethodAsyncTopological analyzes a single method for async operations in topological order
2985
+ func (v *analysisVisitor) analyzeMethodAsyncTopological(methodKey MethodKey, callees []MethodKey) {
2986
+ // Check if method is from handwritten package
2372
2987
  isHandwrittenPackage := v.analysis.isHandwrittenPackage(methodKey.PackagePath)
2373
2988
 
2374
2989
  if isHandwrittenPackage {
2375
2990
  // For handwritten packages, check if we have pre-loaded metadata
2376
- metadataKey := MethodKey{
2377
- PackagePath: methodKey.PackagePath,
2378
- ReceiverType: methodKey.ReceiverType,
2379
- MethodName: methodKey.MethodName,
2991
+ _, hasMetadata := v.analysis.MethodAsyncStatus[methodKey]
2992
+ if hasMetadata {
2993
+ // Already set from metadata, don't override
2994
+ return
2380
2995
  }
2381
- metadataIsAsync, hasMetadata := v.analysis.MethodAsyncStatus[metadataKey]
2996
+ // No metadata means assume sync
2997
+ v.analysis.MethodAsyncStatus[methodKey] = false
2998
+ return
2999
+ }
2382
3000
 
2383
- if hasMetadata {
2384
- // Use explicit metadata from handwritten packages (gs/)
2385
- isAsync = metadataIsAsync
2386
- } else {
2387
- // Handwritten package but no explicit metadata: assume sync
2388
- isAsync = false
3001
+ // Find the method declaration
3002
+ pkg := v.analysis.AllPackages[methodKey.PackagePath]
3003
+ if pkg == nil {
3004
+ if methodKey.PackagePath == v.pkg.Types.Path() {
3005
+ pkg = v.pkg
2389
3006
  }
2390
- } else if funcDecl.Body != nil {
2391
- // Not a handwritten package and has body: always analyze for async operations
2392
- // This allows fixed-point iteration to update results
2393
- isAsync = v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2394
3007
  }
2395
- // Otherwise leave isAsync as false
2396
3008
 
2397
- // Store result in MethodAsyncStatus
2398
- v.analysis.MethodAsyncStatus[methodKey] = isAsync
3009
+ if pkg == nil {
3010
+ // Can't find package, assume sync
3011
+ v.analysis.MethodAsyncStatus[methodKey] = false
3012
+ return
3013
+ }
3014
+
3015
+ var funcDecl *ast.FuncDecl
3016
+ if methodKey.ReceiverType == "" {
3017
+ // Package-level function
3018
+ funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
3019
+ } else {
3020
+ // Method with receiver
3021
+ funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
3022
+ }
3023
+
3024
+ if funcDecl == nil || funcDecl.Body == nil {
3025
+ // No body to analyze, assume sync
3026
+ v.analysis.MethodAsyncStatus[methodKey] = false
3027
+ return
3028
+ }
3029
+
3030
+ // Check if method contains async operations (including calls to async external methods)
3031
+ isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
3032
+
3033
+ // If not directly async, check if any callee from the call graph is async
3034
+ // (This catches calls to other methods in the same codebase)
3035
+ if !isAsync {
3036
+ for _, callee := range callees {
3037
+ if calleeAsync, exists := v.analysis.MethodAsyncStatus[callee]; exists && calleeAsync {
3038
+ isAsync = true
3039
+ break
3040
+ }
3041
+ }
3042
+ }
2399
3043
 
2400
- // Unmark as visiting
2401
- delete(v.visitingMethods, methodKey)
3044
+ v.analysis.MethodAsyncStatus[methodKey] = isAsync
2402
3045
  }
2403
3046
 
2404
3047
  // getMethodKey creates a unique key for a method
@@ -2408,14 +3051,19 @@ func (v *analysisVisitor) getMethodKey(funcDecl *ast.FuncDecl, pkg *packages.Pac
2408
3051
  receiverType := ""
2409
3052
 
2410
3053
  if funcDecl.Recv != nil && len(funcDecl.Recv.List) > 0 {
2411
- // Get receiver type name
2412
- if len(funcDecl.Recv.List[0].Names) > 0 {
3054
+ // Try to get receiver type from TypesInfo first
3055
+ if len(funcDecl.Recv.List[0].Names) > 0 && pkg.TypesInfo != nil {
2413
3056
  if def := pkg.TypesInfo.Defs[funcDecl.Recv.List[0].Names[0]]; def != nil {
2414
3057
  if vr, ok := def.(*types.Var); ok {
2415
3058
  receiverType = v.getTypeName(vr.Type())
2416
3059
  }
2417
3060
  }
2418
3061
  }
3062
+
3063
+ // Fallback to AST if TypesInfo is unavailable or failed
3064
+ if receiverType == "" {
3065
+ receiverType = v.getReceiverTypeFromAST(funcDecl.Recv.List[0].Type)
3066
+ }
2419
3067
  }
2420
3068
 
2421
3069
  return MethodKey{
@@ -2437,6 +3085,23 @@ func (v *analysisVisitor) getTypeName(t types.Type) string {
2437
3085
  }
2438
3086
  }
2439
3087
 
3088
+ // getReceiverTypeFromAST extracts the receiver type name from AST when TypesInfo is unavailable
3089
+ func (v *analysisVisitor) getReceiverTypeFromAST(expr ast.Expr) string {
3090
+ switch t := expr.(type) {
3091
+ case *ast.StarExpr:
3092
+ // Pointer receiver: *Type
3093
+ return v.getReceiverTypeFromAST(t.X)
3094
+ case *ast.Ident:
3095
+ // Simple type name
3096
+ return t.Name
3097
+ case *ast.SelectorExpr:
3098
+ // Qualified type: pkg.Type
3099
+ return t.Sel.Name
3100
+ default:
3101
+ return ""
3102
+ }
3103
+ }
3104
+
2440
3105
  // containsAsyncOperationsComplete is a comprehensive async detection that handles method calls
2441
3106
  func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *packages.Package) bool {
2442
3107
  var hasAsync bool
@@ -2489,7 +3154,13 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
2489
3154
  // Direct function call
2490
3155
  if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
2491
3156
  if funcObj, ok := obj.(*types.Func); ok {
2492
- return v.isFunctionAsync(funcObj, pkg)
3157
+ result := v.isFunctionAsync(funcObj, pkg)
3158
+ return result
3159
+ }
3160
+ // Check if this is a variable that returns async values
3161
+ // (e.g., indirect := sync.OnceValue(asyncFunc))
3162
+ if v.analysis.IsAsyncReturningVar(obj) {
3163
+ return true
2493
3164
  }
2494
3165
  }
2495
3166
 
@@ -2512,14 +3183,16 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
2512
3183
  if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
2513
3184
  methodName := fun.Sel.Name
2514
3185
  // For interface method calls, check if the interface method is async
2515
- return v.analysis.IsInterfaceMethodAsync(interfaceType, methodName)
3186
+ result := v.analysis.IsInterfaceMethodAsync(interfaceType, methodName)
3187
+ return result
2516
3188
  }
2517
3189
  }
2518
3190
 
2519
3191
  // Method call on concrete objects
2520
3192
  if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
2521
3193
  if methodObj := selection.Obj(); methodObj != nil {
2522
- return v.isMethodAsyncFromSelection(fun, methodObj, pkg)
3194
+ result := v.isMethodAsyncFromSelection(fun, methodObj, pkg)
3195
+ return result
2523
3196
  }
2524
3197
  }
2525
3198
  }
@@ -2534,7 +3207,7 @@ func (v *analysisVisitor) isFunctionAsync(funcObj *types.Func, pkg *packages.Pac
2534
3207
  return v.analysis.IsMethodAsync(funcObj.Pkg().Path(), "", funcObj.Name())
2535
3208
  }
2536
3209
 
2537
- // Check internal method status
3210
+ // Check internal method status (should already be computed during analysis)
2538
3211
  methodKey := MethodKey{
2539
3212
  PackagePath: pkg.Types.Path(),
2540
3213
  ReceiverType: "",
@@ -2545,14 +3218,7 @@ func (v *analysisVisitor) isFunctionAsync(funcObj *types.Func, pkg *packages.Pac
2545
3218
  return status
2546
3219
  }
2547
3220
 
2548
- // Not analyzed yet, analyze now
2549
- if funcDecl := v.findFunctionDecl(funcObj.Name(), pkg); funcDecl != nil {
2550
- v.analyzeMethodAsync(funcDecl, pkg)
2551
- if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2552
- return status
2553
- }
2554
- }
2555
-
3221
+ // Not found - should have been analyzed during analyzeAllMethodsAsync
2556
3222
  return false
2557
3223
  }
2558
3224
 
@@ -2572,7 +3238,12 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
2572
3238
  }
2573
3239
  }
2574
3240
  case *ast.SelectorExpr:
2575
- // Field access (e.g., l.m.Lock())
3241
+ // Field access (e.g., l.m.Lock() or d.mu.Lock())
3242
+ if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
3243
+ receiverType = v.getTypeName(typeExpr)
3244
+ }
3245
+ default:
3246
+ // For other cases, try to get type directly
2576
3247
  if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
2577
3248
  receiverType = v.getTypeName(typeExpr)
2578
3249
  }
@@ -2585,7 +3256,6 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
2585
3256
  }
2586
3257
  }
2587
3258
 
2588
- // If no package path found, use current package
2589
3259
  if methodPkgPath == "" {
2590
3260
  methodPkgPath = pkg.Types.Path()
2591
3261
  }
@@ -2602,28 +3272,7 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
2602
3272
  return status
2603
3273
  }
2604
3274
 
2605
- // Check if this is a method in the same package we're currently analyzing
2606
- if methodPkgPath == v.pkg.Types.Path() {
2607
- // This is a method in the same package - we should analyze it if we haven't yet
2608
- if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), v.pkg); funcDecl != nil {
2609
- v.analyzeMethodAsync(funcDecl, v.pkg)
2610
- if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2611
- return status
2612
- }
2613
- }
2614
- } else {
2615
- // For methods in other packages that we're compiling together
2616
- if targetPkg := v.analysis.AllPackages[methodPkgPath]; targetPkg != nil {
2617
- // Try to analyze the method if we haven't already
2618
- if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), targetPkg); funcDecl != nil {
2619
- v.analyzeMethodAsync(funcDecl, targetPkg)
2620
- if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2621
- return status
2622
- }
2623
- }
2624
- }
2625
- }
2626
-
3275
+ // Not found - should have been analyzed during analyzeAllMethodsAsync
2627
3276
  return false
2628
3277
  }
2629
3278
 
@@ -2663,22 +3312,6 @@ func (v *analysisVisitor) findMethodDecl(receiverType, methodName string, pkg *p
2663
3312
  return nil
2664
3313
  }
2665
3314
 
2666
- // checkExternalMethodMetadata checks if an external method is async based on pre-loaded metadata
2667
- func (v *analysisVisitor) checkExternalMethodMetadata(pkgPath, receiverType, methodName string) bool {
2668
- // Use MethodKey to check pre-loaded metadata in MethodAsyncStatus
2669
- key := MethodKey{
2670
- PackagePath: pkgPath,
2671
- ReceiverType: receiverType,
2672
- MethodName: methodName,
2673
- }
2674
-
2675
- if isAsync, exists := v.analysis.MethodAsyncStatus[key]; exists {
2676
- return isAsync
2677
- }
2678
-
2679
- return false
2680
- }
2681
-
2682
3315
  // IsLocalMethodAsync checks if a local method is async using pre-computed analysis
2683
3316
  func (a *Analysis) IsLocalMethodAsync(pkgPath, receiverType, methodName string) bool {
2684
3317
  methodKey := MethodKey{
@@ -2693,31 +3326,3 @@ func (a *Analysis) IsLocalMethodAsync(pkgPath, receiverType, methodName string)
2693
3326
 
2694
3327
  return false
2695
3328
  }
2696
-
2697
- // updateInterfaceImplementationAsyncStatus updates interface implementations with correct async status
2698
- // This runs after method async analysis is complete
2699
- func (v *analysisVisitor) updateInterfaceImplementationAsyncStatus() {
2700
- // Iterate through all tracked interface implementations and update their async status
2701
- for key, implementations := range v.analysis.InterfaceImplementations {
2702
- // Remove duplicates first
2703
- seenMethods := make(map[string]bool)
2704
- uniqueImplementations := []ImplementationInfo{}
2705
-
2706
- for _, impl := range implementations {
2707
- methodKey := impl.StructType.Obj().Name() + "." + key.MethodName
2708
- if !seenMethods[methodKey] {
2709
- seenMethods[methodKey] = true
2710
-
2711
- // Now that method async analysis is complete, get the correct async status
2712
- isAsync := v.analysis.IsAsyncFunc(impl.Method)
2713
-
2714
- // Update the implementation with the correct async status
2715
- impl.IsAsyncByFlow = isAsync
2716
- uniqueImplementations = append(uniqueImplementations, impl)
2717
- }
2718
- }
2719
-
2720
- // Store the updated implementations without duplicates
2721
- v.analysis.InterfaceImplementations[key] = uniqueImplementations
2722
- }
2723
- }