goscript 0.0.59 → 0.0.61

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 (39) hide show
  1. package/compiler/analysis.go +465 -336
  2. package/compiler/compiler.go +10 -4
  3. package/compiler/decl.go +47 -87
  4. package/compiler/expr-call-helpers.go +182 -0
  5. package/compiler/expr-call.go +9 -30
  6. package/compiler/expr-selector.go +38 -1
  7. package/compiler/expr-type.go +37 -1
  8. package/compiler/expr.go +0 -28
  9. package/compiler/spec-struct.go +12 -1
  10. package/compiler/spec.go +21 -1
  11. package/compiler/stmt.go +2 -10
  12. package/compiler/type-utils.go +0 -15
  13. package/dist/gs/builtin/slice.js +6 -7
  14. package/dist/gs/builtin/slice.js.map +1 -1
  15. package/dist/gs/builtin/type.d.ts +5 -0
  16. package/dist/gs/builtin/type.js +7 -0
  17. package/dist/gs/builtin/type.js.map +1 -1
  18. package/dist/gs/internal/byteorder/index.d.ts +6 -0
  19. package/dist/gs/internal/byteorder/index.js +34 -0
  20. package/dist/gs/internal/byteorder/index.js.map +1 -1
  21. package/dist/gs/reflect/index.d.ts +3 -3
  22. package/dist/gs/reflect/index.js +2 -2
  23. package/dist/gs/reflect/index.js.map +1 -1
  24. package/dist/gs/reflect/type.d.ts +9 -0
  25. package/dist/gs/reflect/type.js +73 -0
  26. package/dist/gs/reflect/type.js.map +1 -1
  27. package/dist/gs/slices/slices.d.ts +11 -0
  28. package/dist/gs/slices/slices.js +33 -0
  29. package/dist/gs/slices/slices.js.map +1 -1
  30. package/go.mod +4 -4
  31. package/go.sum +8 -8
  32. package/gs/builtin/slice.ts +6 -7
  33. package/gs/builtin/type.ts +8 -0
  34. package/gs/internal/byteorder/index.ts +40 -0
  35. package/gs/reflect/index.ts +3 -1
  36. package/gs/reflect/type.ts +97 -0
  37. package/gs/slices/slices.ts +37 -0
  38. package/gs/sync/meta.json +1 -1
  39. package/package.json +1 -1
@@ -2,11 +2,11 @@ package compiler
2
2
 
3
3
  import (
4
4
  "encoding/json"
5
- "fmt"
6
5
  "go/ast"
7
6
  "go/token"
8
7
  "go/types"
9
8
  "path/filepath"
9
+ "slices"
10
10
  "strings"
11
11
 
12
12
  "github.com/aperturerobotics/goscript"
@@ -107,9 +107,8 @@ type MethodKey struct {
107
107
 
108
108
  // ImplementationInfo tracks information about a struct that implements an interface method
109
109
  type ImplementationInfo struct {
110
- StructType *types.Named // The struct type that implements the interface
111
- Method *types.Func // The method object
112
- IsAsyncByFlow bool // Whether this implementation is async based on control flow analysis
110
+ StructType *types.Named // The struct type that implements the interface
111
+ Method *types.Func // The method object
113
112
  }
114
113
 
115
114
  // Analysis holds information gathered during the analysis phase of the Go code compilation.
@@ -154,10 +153,6 @@ type Analysis struct {
154
153
  // This is used to determine interface method async status based on implementations
155
154
  InterfaceImplementations map[InterfaceMethodKey][]ImplementationInfo
156
155
 
157
- // InterfaceMethodAsyncStatus caches the async status determination for interface methods
158
- // This is computed once during analysis and reused during code generation
159
- InterfaceMethodAsyncStatus map[InterfaceMethodKey]bool
160
-
161
156
  // MethodAsyncStatus stores the async status of all methods analyzed
162
157
  // This is computed once during analysis and reused during code generation
163
158
  MethodAsyncStatus map[MethodKey]bool
@@ -197,11 +192,10 @@ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
197
192
  ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
198
193
  FunctionAssignments: make(map[types.Object]ast.Node),
199
194
  // PackageMetadata removed - using MethodAsyncStatus only
200
- NamedBasicTypes: make(map[types.Type]bool),
201
- AllPackages: allPackages,
202
- InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
203
- InterfaceMethodAsyncStatus: make(map[InterfaceMethodKey]bool),
204
- MethodAsyncStatus: make(map[MethodKey]bool),
195
+ NamedBasicTypes: make(map[types.Type]bool),
196
+ AllPackages: allPackages,
197
+ InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
198
+ MethodAsyncStatus: make(map[MethodKey]bool),
205
199
  }
206
200
  }
207
201
 
@@ -237,6 +231,22 @@ func (a *Analysis) ensureFunctionData(obj types.Object) *FunctionInfo {
237
231
  return a.FunctionData[obj]
238
232
  }
239
233
 
234
+ // GetFunctionInfoFromContext returns FunctionInfo based on the enclosing function context
235
+ func (a *Analysis) GetFunctionInfoFromContext(nodeInfo *NodeInfo, pkg *packages.Package) *FunctionInfo {
236
+ if nodeInfo == nil {
237
+ return nil
238
+ }
239
+ if nodeInfo.EnclosingFuncDecl != nil {
240
+ if obj := pkg.TypesInfo.ObjectOf(nodeInfo.EnclosingFuncDecl.Name); obj != nil {
241
+ return a.FunctionData[obj]
242
+ }
243
+ }
244
+ if nodeInfo.EnclosingFuncLit != nil {
245
+ return a.FuncLitData[nodeInfo.EnclosingFuncLit]
246
+ }
247
+ return nil
248
+ }
249
+
240
250
  // NeedsDefer returns whether the given node needs defer handling.
241
251
  func (a *Analysis) NeedsDefer(node ast.Node) bool {
242
252
  if node == nil {
@@ -371,7 +381,7 @@ func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
371
381
  }
372
382
 
373
383
  // For pointer variables, check if they point to a variable-referenced value
374
- if ptrType, ok := obj.Type().(*types.Pointer); ok {
384
+ if _, ok := obj.Type().(*types.Pointer); ok {
375
385
  // Check all assignments to this pointer variable
376
386
  for varObj, info := range a.VariableUsage {
377
387
  if varObj == obj {
@@ -383,11 +393,6 @@ func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
383
393
  }
384
394
  }
385
395
  }
386
-
387
- // Handle direct pointer initialization like: var p *int = &x
388
- // Check if the pointer type's element type requires variable referencing
389
- _ = ptrType.Elem()
390
- // For now, conservatively return false for untracked cases
391
396
  }
392
397
 
393
398
  return false
@@ -454,9 +459,6 @@ type analysisVisitor struct {
454
459
 
455
460
  // currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
456
461
  currentFuncLit *ast.FuncLit
457
-
458
- // visitingMethods tracks methods currently being analyzed to prevent infinite recursion
459
- visitingMethods map[MethodKey]bool
460
462
  }
461
463
 
462
464
  // getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
@@ -923,18 +925,8 @@ func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
923
925
 
924
926
  // Check if it's a bare return
925
927
  if len(n.Results) == 0 {
926
- if v.currentFuncDecl != nil {
927
- // Check if the enclosing function declaration has named returns
928
- if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
929
- if _, ok := v.analysis.FunctionData[obj]; ok {
930
- nodeInfo.IsBareReturn = true
931
- }
932
- }
933
- } else if v.currentFuncLit != nil {
934
- // Check if the enclosing function literal has named returns
935
- if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
936
- nodeInfo.IsBareReturn = true
937
- }
928
+ if v.analysis.GetFunctionInfoFromContext(nodeInfo, v.pkg) != nil {
929
+ nodeInfo.IsBareReturn = true
938
930
  }
939
931
  }
940
932
  return v
@@ -1012,14 +1004,8 @@ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) as
1012
1004
  // Find the corresponding method in the struct type
1013
1005
  structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
1014
1006
  if structMethod != nil {
1015
- // Determine if this struct method is async using unified system
1016
- isAsync := false
1017
- if obj := structMethod; obj != nil {
1018
- isAsync = v.analysis.IsAsyncFunc(obj)
1019
- }
1020
-
1021
1007
  // Track this interface implementation
1022
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
1008
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
1023
1009
  }
1024
1010
  }
1025
1011
  return v
@@ -1183,9 +1169,8 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
1183
1169
 
1184
1170
  // Create visitor for the entire package
1185
1171
  visitor := &analysisVisitor{
1186
- analysis: analysis,
1187
- pkg: pkg,
1188
- visitingMethods: make(map[MethodKey]bool),
1172
+ analysis: analysis,
1173
+ pkg: pkg,
1189
1174
  }
1190
1175
 
1191
1176
  // First pass: analyze all declarations and statements across all files
@@ -1277,13 +1262,7 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1277
1262
 
1278
1263
  // Check if this function is defined in the current file
1279
1264
  currentFileFuncs := analysis.FunctionDefs[baseFileName]
1280
- isDefinedInCurrentFile := false
1281
- for _, f := range currentFileFuncs {
1282
- if f == funcName {
1283
- isDefinedInCurrentFile = true
1284
- break
1285
- }
1286
- }
1265
+ isDefinedInCurrentFile := slices.Contains(currentFileFuncs, funcName)
1287
1266
 
1288
1267
  // If not defined in current file, find which file defines it
1289
1268
  if !isDefinedInCurrentFile {
@@ -1291,24 +1270,15 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1291
1270
  if sourceFile == baseFileName {
1292
1271
  continue // Skip current file
1293
1272
  }
1294
- for _, f := range funcs {
1295
- if f == funcName {
1296
- // Found the function in another file
1297
- if callsFromOtherFiles[sourceFile] == nil {
1298
- callsFromOtherFiles[sourceFile] = []string{}
1299
- }
1300
- // Check if already added to avoid duplicates
1301
- found := false
1302
- for _, existing := range callsFromOtherFiles[sourceFile] {
1303
- if existing == funcName {
1304
- found = true
1305
- break
1306
- }
1307
- }
1308
- if !found {
1309
- callsFromOtherFiles[sourceFile] = append(callsFromOtherFiles[sourceFile], funcName)
1310
- }
1311
- break
1273
+ if slices.Contains(funcs, funcName) {
1274
+ // Found the function in another file
1275
+ if callsFromOtherFiles[sourceFile] == nil {
1276
+ callsFromOtherFiles[sourceFile] = []string{}
1277
+ }
1278
+ // Check if already added to avoid duplicates
1279
+ found := slices.Contains(callsFromOtherFiles[sourceFile], funcName)
1280
+ if !found {
1281
+ callsFromOtherFiles[sourceFile] = append(callsFromOtherFiles[sourceFile], funcName)
1312
1282
  }
1313
1283
  }
1314
1284
  }
@@ -1341,13 +1311,7 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1341
1311
 
1342
1312
  // Check if this type is defined in the current file
1343
1313
  currentFileTypes := analysis.TypeDefs[baseFileName]
1344
- isDefinedInCurrentFile := false
1345
- for _, t := range currentFileTypes {
1346
- if t == typeName {
1347
- isDefinedInCurrentFile = true
1348
- break
1349
- }
1350
- }
1314
+ isDefinedInCurrentFile := slices.Contains(currentFileTypes, typeName)
1351
1315
 
1352
1316
  // If not defined in current file, find which file defines it
1353
1317
  if !isDefinedInCurrentFile {
@@ -1355,24 +1319,15 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1355
1319
  if sourceFile == baseFileName {
1356
1320
  continue // Skip current file
1357
1321
  }
1358
- for _, t := range types {
1359
- if t == typeName {
1360
- // Found the type in another file
1361
- if typeRefsFromOtherFiles[sourceFile] == nil {
1362
- typeRefsFromOtherFiles[sourceFile] = []string{}
1363
- }
1364
- // Check if already added to avoid duplicates
1365
- found := false
1366
- for _, existing := range typeRefsFromOtherFiles[sourceFile] {
1367
- if existing == typeName {
1368
- found = true
1369
- break
1370
- }
1371
- }
1372
- if !found {
1373
- typeRefsFromOtherFiles[sourceFile] = append(typeRefsFromOtherFiles[sourceFile], typeName)
1374
- }
1375
- break
1322
+ if slices.Contains(types, typeName) {
1323
+ // Found the type in another file
1324
+ if typeRefsFromOtherFiles[sourceFile] == nil {
1325
+ typeRefsFromOtherFiles[sourceFile] = []string{}
1326
+ }
1327
+ // Check if already added to avoid duplicates
1328
+ found := slices.Contains(typeRefsFromOtherFiles[sourceFile], typeName)
1329
+ if !found {
1330
+ typeRefsFromOtherFiles[sourceFile] = append(typeRefsFromOtherFiles[sourceFile], typeName)
1376
1331
  }
1377
1332
  }
1378
1333
  }
@@ -1472,6 +1427,14 @@ func (a *Analysis) loadGsMetadata(metaFilePath string) *GsMetadata {
1472
1427
  return &metadata
1473
1428
  }
1474
1429
 
1430
+ // isHandwrittenPackage checks if a package path corresponds to a handwritten package in gs/
1431
+ func (a *Analysis) isHandwrittenPackage(pkgPath string) bool {
1432
+ // Check if the package exists in the embedded gs/ directory
1433
+ metaFilePath := filepath.Join("gs", pkgPath, "meta.json")
1434
+ _, err := goscript.GsOverrides.ReadFile(metaFilePath)
1435
+ return err == nil
1436
+ }
1437
+
1475
1438
  // IsMethodAsync checks if a method call is async based on package metadata
1476
1439
  func (a *Analysis) IsMethodAsync(pkgPath, typeName, methodName string) bool {
1477
1440
  // First, check pre-computed method async status
@@ -1688,16 +1651,15 @@ func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map
1688
1651
  }
1689
1652
 
1690
1653
  // trackInterfaceImplementation records that a struct type implements an interface method
1691
- func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func, isAsync bool) {
1654
+ func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func) {
1692
1655
  key := InterfaceMethodKey{
1693
1656
  InterfaceType: interfaceType.String(),
1694
1657
  MethodName: method.Name(),
1695
1658
  }
1696
1659
 
1697
1660
  implementation := ImplementationInfo{
1698
- StructType: structType,
1699
- Method: method,
1700
- IsAsyncByFlow: isAsync,
1661
+ StructType: structType,
1662
+ Method: method,
1701
1663
  }
1702
1664
 
1703
1665
  a.InterfaceImplementations[key] = append(a.InterfaceImplementations[key], implementation)
@@ -1710,49 +1672,25 @@ func (a *Analysis) IsInterfaceMethodAsync(interfaceType *types.Interface, method
1710
1672
  MethodName: methodName,
1711
1673
  }
1712
1674
 
1713
- // Check if we've already computed this
1714
- if result, exists := a.InterfaceMethodAsyncStatus[key]; exists {
1715
- return result
1716
- }
1717
-
1718
1675
  // Find all implementations of this interface method
1719
1676
  implementations, exists := a.InterfaceImplementations[key]
1720
1677
  if !exists {
1721
- // No implementations found, default to sync
1722
- a.InterfaceMethodAsyncStatus[key] = false
1723
1678
  return false
1724
1679
  }
1725
1680
 
1726
- // Update implementations with current async status before checking
1727
- for i := range implementations {
1728
- impl := &implementations[i]
1729
-
1730
- // Create method key for this implementation
1681
+ // If ANY implementation is async, the interface method is async
1682
+ for _, impl := range implementations {
1731
1683
  methodKey := MethodKey{
1732
1684
  PackagePath: impl.StructType.Obj().Pkg().Path(),
1733
1685
  ReceiverType: impl.StructType.Obj().Name(),
1734
1686
  MethodName: impl.Method.Name(),
1735
1687
  }
1736
1688
 
1737
- // Update with current async status from method analysis
1738
- if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists {
1739
- impl.IsAsyncByFlow = isAsync
1740
- }
1741
- }
1742
-
1743
- // Store the updated implementations back to the map
1744
- a.InterfaceImplementations[key] = implementations
1745
-
1746
- // If ANY implementation is async, the interface method is async
1747
- for _, impl := range implementations {
1748
- if impl.IsAsyncByFlow {
1749
- a.InterfaceMethodAsyncStatus[key] = true
1689
+ if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists && isAsync {
1750
1690
  return true
1751
1691
  }
1752
1692
  }
1753
1693
 
1754
- // All implementations are sync
1755
- a.InterfaceMethodAsyncStatus[key] = false
1756
1694
  return false
1757
1695
  }
1758
1696
 
@@ -1837,56 +1775,6 @@ func (a *Analysis) GetIdentifierMapping(ident *ast.Ident) string {
1837
1775
  return ""
1838
1776
  }
1839
1777
 
1840
- // trackTypeAssertion analyzes type assertions and records interface implementations
1841
- func (v *analysisVisitor) trackTypeAssertion(typeAssert *ast.TypeAssertExpr) {
1842
- // Get the type being asserted to
1843
- assertedType := v.pkg.TypesInfo.TypeOf(typeAssert.Type)
1844
- if assertedType == nil {
1845
- return
1846
- }
1847
-
1848
- // Check if the asserted type is an interface
1849
- interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
1850
- if !isInterface {
1851
- return
1852
- }
1853
-
1854
- // Get the type of the expression being asserted
1855
- exprType := v.pkg.TypesInfo.TypeOf(typeAssert.X)
1856
- if exprType == nil {
1857
- return
1858
- }
1859
-
1860
- // Handle pointer types by getting the element type
1861
- if ptrType, isPtr := exprType.(*types.Pointer); isPtr {
1862
- exprType = ptrType.Elem()
1863
- }
1864
-
1865
- // Check if the expression type is a named struct type
1866
- namedType, isNamed := exprType.(*types.Named)
1867
- if !isNamed {
1868
- return
1869
- }
1870
-
1871
- // For each method in the interface, check if the struct implements it
1872
- for i := 0; i < interfaceType.NumExplicitMethods(); i++ {
1873
- interfaceMethod := interfaceType.ExplicitMethod(i)
1874
-
1875
- // Find the corresponding method in the struct type
1876
- structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
1877
- if structMethod != nil {
1878
- // Determine if this struct method is async using unified system
1879
- isAsync := false
1880
- if obj := structMethod; obj != nil {
1881
- isAsync = v.analysis.IsAsyncFunc(obj)
1882
- }
1883
-
1884
- // Track this interface implementation
1885
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
1886
- }
1887
- }
1888
- }
1889
-
1890
1778
  // findStructMethod finds a method with the given name on a named type
1891
1779
  func (v *analysisVisitor) findStructMethod(namedType *types.Named, methodName string) *types.Func {
1892
1780
  // Check methods directly on the type
@@ -1999,10 +1887,7 @@ func (v *analysisVisitor) trackInterfaceAssignments(assignStmt *ast.AssignStmt)
1999
1887
 
2000
1888
  structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
2001
1889
  if structMethod != nil {
2002
- // Determine if this struct method is async using unified system
2003
- isAsync := v.analysis.IsAsyncFunc(structMethod)
2004
-
2005
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
1890
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
2006
1891
  }
2007
1892
  }
2008
1893
  }
@@ -2061,9 +1946,7 @@ func (v *analysisVisitor) trackInterfaceCallArguments(callExpr *ast.CallExpr) {
2061
1946
 
2062
1947
  structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
2063
1948
  if structMethod != nil {
2064
- // Note: Don't determine async status here - it will be determined later after method analysis
2065
- // For now, just track the implementation relationship without async status
2066
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, false)
1949
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
2067
1950
  }
2068
1951
  }
2069
1952
  }
@@ -2209,10 +2092,7 @@ func (v *interfaceImplementationVisitor) trackImplementation(interfaceType *type
2209
2092
  // Find the method in the implementing type
2210
2093
  structMethod := v.findMethodInType(namedType, interfaceMethod.Name())
2211
2094
  if structMethod != nil {
2212
- // Determine if this implementation is async using unified system
2213
- isAsync := v.analysis.IsAsyncFunc(structMethod)
2214
-
2215
- v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
2095
+ v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
2216
2096
  }
2217
2097
  }
2218
2098
  }
@@ -2228,52 +2108,77 @@ func (v *interfaceImplementationVisitor) findMethodInType(namedType *types.Named
2228
2108
  return nil
2229
2109
  }
2230
2110
 
2231
- // getNamedReturns retrieves the named returns for a function
2232
- func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
2233
- var namedReturns []string
2234
- if funcDecl.Type != nil && funcDecl.Type.Results != nil {
2235
- for _, field := range funcDecl.Type.Results.List {
2236
- for _, name := range field.Names {
2237
- namedReturns = append(namedReturns, name.Name)
2111
+ // analyzeAllMethodsAsync performs comprehensive async analysis on all methods in all packages using topological sort
2112
+ func (v *analysisVisitor) analyzeAllMethodsAsync() {
2113
+ // Build the method call graph for all packages
2114
+ methodCalls := v.buildMethodCallGraph()
2115
+
2116
+ // Topologically sort methods by their dependencies
2117
+ sorted, cycles := v.topologicalSortMethods(methodCalls)
2118
+
2119
+ // Mark methods in cycles - check if they contain async operations
2120
+ // We can't rely on the call graph for methods in cycles (circular dependency),
2121
+ // but we can still detect if they call async external methods
2122
+ //
2123
+ // We need to iterate multiple times because methods in cycles can call each other,
2124
+ // and we need to propagate async status until no changes occur
2125
+ maxIterations := 10
2126
+ for iteration := 0; iteration < maxIterations; iteration++ {
2127
+ changed := false
2128
+
2129
+ for _, methodKey := range cycles {
2130
+ // For methods in cycles, we need to check their body directly for async operations
2131
+ pkg := v.analysis.AllPackages[methodKey.PackagePath]
2132
+ if pkg == nil && methodKey.PackagePath == v.pkg.Types.Path() {
2133
+ pkg = v.pkg
2238
2134
  }
2239
- }
2240
- }
2241
- return namedReturns
2242
- }
2243
2135
 
2244
- // analyzeAllMethodsAsync performs comprehensive async analysis on all methods in all packages
2245
- func (v *analysisVisitor) analyzeAllMethodsAsync() {
2246
- // Initialize visitingMethods map
2247
- v.visitingMethods = make(map[MethodKey]bool)
2136
+ if pkg != nil {
2137
+ var funcDecl *ast.FuncDecl
2138
+ if methodKey.ReceiverType == "" {
2139
+ funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
2140
+ } else {
2141
+ funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
2142
+ }
2248
2143
 
2249
- // Analyze methods in current package
2250
- v.analyzePackageMethodsAsync(v.pkg)
2144
+ if funcDecl != nil && funcDecl.Body != nil {
2145
+ // Check if the method contains async operations (including calls to async external methods)
2146
+ isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2251
2147
 
2252
- // Analyze methods in all dependency packages
2253
- for _, pkg := range v.analysis.AllPackages {
2254
- if pkg != v.pkg {
2255
- v.analyzePackageMethodsAsync(pkg)
2148
+ // Check if status changed
2149
+ if oldStatus, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists || oldStatus != isAsync {
2150
+ changed = true
2151
+ }
2152
+
2153
+ v.analysis.MethodAsyncStatus[methodKey] = isAsync
2154
+ continue
2155
+ }
2156
+ }
2157
+
2158
+ // Fallback: mark as sync if we can't analyze the body
2159
+ if _, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists {
2160
+ v.analysis.MethodAsyncStatus[methodKey] = false
2161
+ changed = true
2162
+ }
2163
+ }
2164
+
2165
+ // If no changes in this iteration, we're done
2166
+ if !changed {
2167
+ break
2256
2168
  }
2257
2169
  }
2258
2170
 
2171
+ // Analyze methods in dependency order (dependencies analyzed before dependents)
2172
+ for _, methodKey := range sorted {
2173
+ v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
2174
+ }
2175
+
2259
2176
  // Finally, analyze function literals in the current package only
2260
2177
  // (external packages' function literals are not accessible)
2261
2178
  v.analyzeFunctionLiteralsAsync(v.pkg)
2262
2179
  }
2263
2180
 
2264
- // analyzePackageMethodsAsync analyzes all methods in a specific package
2265
- func (v *analysisVisitor) analyzePackageMethodsAsync(pkg *packages.Package) {
2266
- // Analyze function declarations
2267
- for _, file := range pkg.Syntax {
2268
- for _, decl := range file.Decls {
2269
- if funcDecl, ok := decl.(*ast.FuncDecl); ok {
2270
- v.analyzeMethodAsync(funcDecl, pkg)
2271
- }
2272
- }
2273
- }
2274
- }
2275
-
2276
- // analyzeFunctionLiteralsAsync analyzes all function literals in a package for async operations
2181
+ // buildMethodCallGraph builds a graph of which methods call which other methods
2277
2182
  func (v *analysisVisitor) analyzeFunctionLiteralsAsync(pkg *packages.Package) {
2278
2183
  for _, file := range pkg.Syntax {
2279
2184
  ast.Inspect(file, func(n ast.Node) bool {
@@ -2307,45 +2212,320 @@ func (v *analysisVisitor) analyzeFunctionLiteralAsync(funcLit *ast.FuncLit, pkg
2307
2212
  nodeInfo.InAsyncContext = isAsync
2308
2213
  }
2309
2214
 
2310
- // analyzeMethodAsync determines if a method is async and stores the result
2311
- func (v *analysisVisitor) analyzeMethodAsync(funcDecl *ast.FuncDecl, pkg *packages.Package) {
2312
- methodKey := v.getMethodKey(funcDecl, pkg)
2215
+ // buildMethodCallGraph builds a graph of which methods call which other methods
2216
+ func (v *analysisVisitor) buildMethodCallGraph() map[MethodKey][]MethodKey {
2217
+ methodCalls := make(map[MethodKey][]MethodKey)
2313
2218
 
2314
- // Check if already analyzed
2315
- if _, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2316
- return
2219
+ // Iterate through all packages
2220
+ allPkgs := []*packages.Package{v.pkg}
2221
+ for _, pkg := range v.analysis.AllPackages {
2222
+ if pkg != v.pkg {
2223
+ allPkgs = append(allPkgs, pkg)
2224
+ }
2225
+ }
2226
+
2227
+ for _, pkg := range allPkgs {
2228
+ for _, file := range pkg.Syntax {
2229
+ for _, decl := range file.Decls {
2230
+ if funcDecl, ok := decl.(*ast.FuncDecl); ok {
2231
+ methodKey := v.getMethodKey(funcDecl, pkg)
2232
+
2233
+ // Initialize the entry for this method
2234
+ if _, exists := methodCalls[methodKey]; !exists {
2235
+ methodCalls[methodKey] = []MethodKey{}
2236
+ }
2237
+
2238
+ // Extract method calls from the function body
2239
+ if funcDecl.Body != nil {
2240
+ callees := v.extractMethodCalls(funcDecl.Body, pkg)
2241
+ methodCalls[methodKey] = callees
2242
+ }
2243
+ }
2244
+ }
2245
+ }
2246
+ }
2247
+
2248
+ return methodCalls
2249
+ }
2250
+
2251
+ // extractMethodCalls extracts all method and function calls from a node
2252
+ func (v *analysisVisitor) extractMethodCalls(node ast.Node, pkg *packages.Package) []MethodKey {
2253
+ var calls []MethodKey
2254
+ seen := make(map[MethodKey]bool)
2255
+
2256
+ ast.Inspect(node, func(n ast.Node) bool {
2257
+ if n == nil {
2258
+ return false
2259
+ }
2260
+
2261
+ if callExpr, ok := n.(*ast.CallExpr); ok {
2262
+ methodKeys := v.extractMethodKeysFromCall(callExpr, pkg)
2263
+ for _, methodKey := range methodKeys {
2264
+ if !seen[methodKey] {
2265
+ seen[methodKey] = true
2266
+ calls = append(calls, methodKey)
2267
+ }
2268
+ }
2269
+ }
2270
+
2271
+ return true
2272
+ })
2273
+
2274
+ return calls
2275
+ }
2276
+
2277
+ // extractMethodKeysFromCall extracts MethodKeys from a call expression
2278
+ // Returns multiple keys for interface method calls (one for each implementation)
2279
+ func (v *analysisVisitor) extractMethodKeysFromCall(callExpr *ast.CallExpr, pkg *packages.Package) []MethodKey {
2280
+ singleKey := v.extractMethodKeyFromCall(callExpr, pkg)
2281
+ if singleKey != nil {
2282
+ return []MethodKey{*singleKey}
2283
+ }
2284
+
2285
+ // Check if this is an interface method call
2286
+ if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
2287
+ if receiverType := pkg.TypesInfo.TypeOf(selExpr.X); receiverType != nil {
2288
+ if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
2289
+ methodName := selExpr.Sel.Name
2290
+ // Find all implementations of this interface method
2291
+ key := InterfaceMethodKey{
2292
+ InterfaceType: interfaceType.String(),
2293
+ MethodName: methodName,
2294
+ }
2295
+ if implementations, exists := v.analysis.InterfaceImplementations[key]; exists {
2296
+ var keys []MethodKey
2297
+ for _, impl := range implementations {
2298
+ keys = append(keys, MethodKey{
2299
+ PackagePath: impl.StructType.Obj().Pkg().Path(),
2300
+ ReceiverType: impl.StructType.Obj().Name(),
2301
+ MethodName: impl.Method.Name(),
2302
+ })
2303
+ }
2304
+ return keys
2305
+ }
2306
+ }
2307
+ }
2308
+ }
2309
+
2310
+ return nil
2311
+ }
2312
+
2313
+ // extractMethodKeyFromCall extracts a MethodKey from a call expression
2314
+ func (v *analysisVisitor) extractMethodKeyFromCall(callExpr *ast.CallExpr, pkg *packages.Package) *MethodKey {
2315
+ switch fun := callExpr.Fun.(type) {
2316
+ case *ast.Ident:
2317
+ // Direct function call
2318
+ if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
2319
+ if funcObj, ok := obj.(*types.Func); ok {
2320
+ pkgPath := pkg.Types.Path()
2321
+ if funcObj.Pkg() != nil {
2322
+ pkgPath = funcObj.Pkg().Path()
2323
+ }
2324
+ return &MethodKey{
2325
+ PackagePath: pkgPath,
2326
+ ReceiverType: "",
2327
+ MethodName: funcObj.Name(),
2328
+ }
2329
+ }
2330
+ }
2331
+
2332
+ case *ast.SelectorExpr:
2333
+ // Package-level function call (e.g., time.Sleep)
2334
+ if ident, ok := fun.X.(*ast.Ident); ok {
2335
+ if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
2336
+ if pkgName, isPkg := obj.(*types.PkgName); isPkg {
2337
+ return &MethodKey{
2338
+ PackagePath: pkgName.Imported().Path(),
2339
+ ReceiverType: "",
2340
+ MethodName: fun.Sel.Name,
2341
+ }
2342
+ }
2343
+ }
2344
+ }
2345
+
2346
+ // Check if this is an interface method call - if so, return nil
2347
+ // so extractMethodKeysFromCall can expand it to all implementations
2348
+ if receiverType := pkg.TypesInfo.TypeOf(fun.X); receiverType != nil {
2349
+ if _, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
2350
+ // This is an interface method call - return nil to let
2351
+ // extractMethodKeysFromCall handle expanding to implementations
2352
+ return nil
2353
+ }
2354
+ }
2355
+
2356
+ // Method call on concrete objects
2357
+ if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
2358
+ if methodObj := selection.Obj(); methodObj != nil {
2359
+ receiverType := ""
2360
+ methodPkgPath := ""
2361
+
2362
+ // Get receiver type
2363
+ switch x := fun.X.(type) {
2364
+ case *ast.Ident:
2365
+ if obj := pkg.TypesInfo.Uses[x]; obj != nil {
2366
+ if varObj, ok := obj.(*types.Var); ok {
2367
+ receiverType = v.getTypeName(varObj.Type())
2368
+ }
2369
+ }
2370
+ case *ast.SelectorExpr:
2371
+ if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
2372
+ receiverType = v.getTypeName(typeExpr)
2373
+ }
2374
+ }
2375
+
2376
+ // Get method's package path
2377
+ if methodFunc, ok := methodObj.(*types.Func); ok {
2378
+ if methodFunc.Pkg() != nil {
2379
+ methodPkgPath = methodFunc.Pkg().Path()
2380
+ }
2381
+ }
2382
+
2383
+ if methodPkgPath == "" {
2384
+ methodPkgPath = pkg.Types.Path()
2385
+ }
2386
+
2387
+ return &MethodKey{
2388
+ PackagePath: methodPkgPath,
2389
+ ReceiverType: receiverType,
2390
+ MethodName: methodObj.Name(),
2391
+ }
2392
+ }
2393
+ }
2394
+ }
2395
+
2396
+ return nil
2397
+ }
2398
+
2399
+ // topologicalSortMethods performs a topological sort of methods based on their call dependencies
2400
+ // Returns sorted methods and methods involved in cycles
2401
+ func (v *analysisVisitor) topologicalSortMethods(methodCalls map[MethodKey][]MethodKey) ([]MethodKey, []MethodKey) {
2402
+ // Kahn's algorithm for topological sorting
2403
+ inDegree := make(map[MethodKey]int)
2404
+ graph := make(map[MethodKey][]MethodKey)
2405
+
2406
+ // Initialize in-degree counts and reverse graph
2407
+ for method := range methodCalls {
2408
+ inDegree[method] = 0
2409
+ graph[method] = []MethodKey{}
2410
+ }
2411
+
2412
+ // Build reverse graph and count in-degrees
2413
+ // graph[dependency] = methods that depend on it
2414
+ for method, callees := range methodCalls {
2415
+ for _, callee := range callees {
2416
+ // Only create dependency edges for callees that exist in the call graph
2417
+ // This automatically excludes handwritten packages that aren't being compiled
2418
+ if _, exists := inDegree[callee]; exists {
2419
+ // Additionally, skip if the callee is from a handwritten package
2420
+ // This prevents our code from being blocked by unresolved handwritten package dependencies
2421
+ if !v.analysis.isHandwrittenPackage(callee.PackagePath) {
2422
+ graph[callee] = append(graph[callee], method)
2423
+ inDegree[method]++
2424
+ }
2425
+ }
2426
+ }
2427
+ }
2428
+
2429
+ // Find methods with no dependencies (in-degree == 0)
2430
+ var queue []MethodKey
2431
+ for method, degree := range inDegree {
2432
+ if degree == 0 {
2433
+ queue = append(queue, method)
2434
+ }
2435
+ }
2436
+
2437
+ var sorted []MethodKey
2438
+
2439
+ for len(queue) > 0 {
2440
+ // Remove method from queue
2441
+ current := queue[0]
2442
+ queue = queue[1:]
2443
+ sorted = append(sorted, current)
2444
+
2445
+ // For each method that depends on current
2446
+ for _, dependent := range graph[current] {
2447
+ inDegree[dependent]--
2448
+ if inDegree[dependent] == 0 {
2449
+ queue = append(queue, dependent)
2450
+ }
2451
+ }
2317
2452
  }
2318
2453
 
2319
- // Check for cycles
2320
- if v.visitingMethods[methodKey] {
2321
- // Cycle detected, assume sync to break recursion
2454
+ // Find methods in cycles (not in sorted list)
2455
+ var cycles []MethodKey
2456
+ if len(sorted) != len(methodCalls) {
2457
+ for method := range methodCalls {
2458
+ found := slices.Contains(sorted, method)
2459
+ if !found {
2460
+ cycles = append(cycles, method)
2461
+ }
2462
+ }
2463
+ }
2464
+
2465
+ return sorted, cycles
2466
+ }
2467
+
2468
+ // analyzeMethodAsyncTopological analyzes a single method for async operations in topological order
2469
+ func (v *analysisVisitor) analyzeMethodAsyncTopological(methodKey MethodKey, callees []MethodKey) {
2470
+ // Check if method is from handwritten package
2471
+ isHandwrittenPackage := v.analysis.isHandwrittenPackage(methodKey.PackagePath)
2472
+
2473
+ if isHandwrittenPackage {
2474
+ // For handwritten packages, check if we have pre-loaded metadata
2475
+ _, hasMetadata := v.analysis.MethodAsyncStatus[methodKey]
2476
+ if hasMetadata {
2477
+ // Already set from metadata, don't override
2478
+ return
2479
+ }
2480
+ // No metadata means assume sync
2322
2481
  v.analysis.MethodAsyncStatus[methodKey] = false
2323
2482
  return
2324
2483
  }
2325
2484
 
2326
- // Mark as visiting
2327
- v.visitingMethods[methodKey] = true
2485
+ // Find the method declaration
2486
+ pkg := v.analysis.AllPackages[methodKey.PackagePath]
2487
+ if pkg == nil {
2488
+ if methodKey.PackagePath == v.pkg.Types.Path() {
2489
+ pkg = v.pkg
2490
+ }
2491
+ }
2328
2492
 
2329
- // Determine if method is async
2330
- isAsync := false
2493
+ if pkg == nil {
2494
+ // Can't find package, assume sync
2495
+ v.analysis.MethodAsyncStatus[methodKey] = false
2496
+ return
2497
+ }
2331
2498
 
2332
- // Determine if this is a truly external package vs a package being compiled locally
2333
- isExternalPackage := pkg.Types != v.pkg.Types && v.analysis.AllPackages[pkg.Types.Path()] == nil
2499
+ var funcDecl *ast.FuncDecl
2500
+ if methodKey.ReceiverType == "" {
2501
+ // Package-level function
2502
+ funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
2503
+ } else {
2504
+ // Method with receiver
2505
+ funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
2506
+ }
2334
2507
 
2335
- if isExternalPackage {
2336
- // Truly external package: check metadata first
2337
- isAsync = v.checkExternalMethodMetadata(methodKey.PackagePath, methodKey.ReceiverType, methodKey.MethodName)
2508
+ if funcDecl == nil || funcDecl.Body == nil {
2509
+ // No body to analyze, assume sync
2510
+ v.analysis.MethodAsyncStatus[methodKey] = false
2511
+ return
2338
2512
  }
2339
- // If not determined async yet and body exists, analyze it
2340
- if !isAsync && funcDecl.Body != nil {
2341
- isAsync = v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2513
+
2514
+ // Check if method contains async operations (including calls to async external methods)
2515
+ isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2516
+
2517
+ // If not directly async, check if any callee from the call graph is async
2518
+ // (This catches calls to other methods in the same codebase)
2519
+ if !isAsync {
2520
+ for _, callee := range callees {
2521
+ if calleeAsync, exists := v.analysis.MethodAsyncStatus[callee]; exists && calleeAsync {
2522
+ isAsync = true
2523
+ break
2524
+ }
2525
+ }
2342
2526
  }
2343
2527
 
2344
- // Store result in MethodAsyncStatus
2345
2528
  v.analysis.MethodAsyncStatus[methodKey] = isAsync
2346
-
2347
- // Unmark as visiting
2348
- delete(v.visitingMethods, methodKey)
2349
2529
  }
2350
2530
 
2351
2531
  // getMethodKey creates a unique key for a method
@@ -2355,14 +2535,19 @@ func (v *analysisVisitor) getMethodKey(funcDecl *ast.FuncDecl, pkg *packages.Pac
2355
2535
  receiverType := ""
2356
2536
 
2357
2537
  if funcDecl.Recv != nil && len(funcDecl.Recv.List) > 0 {
2358
- // Get receiver type name
2359
- if len(funcDecl.Recv.List[0].Names) > 0 {
2538
+ // Try to get receiver type from TypesInfo first
2539
+ if len(funcDecl.Recv.List[0].Names) > 0 && pkg.TypesInfo != nil {
2360
2540
  if def := pkg.TypesInfo.Defs[funcDecl.Recv.List[0].Names[0]]; def != nil {
2361
2541
  if vr, ok := def.(*types.Var); ok {
2362
2542
  receiverType = v.getTypeName(vr.Type())
2363
2543
  }
2364
2544
  }
2365
2545
  }
2546
+
2547
+ // Fallback to AST if TypesInfo is unavailable or failed
2548
+ if receiverType == "" {
2549
+ receiverType = v.getReceiverTypeFromAST(funcDecl.Recv.List[0].Type)
2550
+ }
2366
2551
  }
2367
2552
 
2368
2553
  return MethodKey{
@@ -2384,6 +2569,23 @@ func (v *analysisVisitor) getTypeName(t types.Type) string {
2384
2569
  }
2385
2570
  }
2386
2571
 
2572
+ // getReceiverTypeFromAST extracts the receiver type name from AST when TypesInfo is unavailable
2573
+ func (v *analysisVisitor) getReceiverTypeFromAST(expr ast.Expr) string {
2574
+ switch t := expr.(type) {
2575
+ case *ast.StarExpr:
2576
+ // Pointer receiver: *Type
2577
+ return v.getReceiverTypeFromAST(t.X)
2578
+ case *ast.Ident:
2579
+ // Simple type name
2580
+ return t.Name
2581
+ case *ast.SelectorExpr:
2582
+ // Qualified type: pkg.Type
2583
+ return t.Sel.Name
2584
+ default:
2585
+ return ""
2586
+ }
2587
+ }
2588
+
2387
2589
  // containsAsyncOperationsComplete is a comprehensive async detection that handles method calls
2388
2590
  func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *packages.Package) bool {
2389
2591
  var hasAsync bool
@@ -2417,16 +2619,8 @@ func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *pa
2417
2619
 
2418
2620
  case *ast.CallExpr:
2419
2621
  // Check if we're calling a function known to be async
2420
- isCallAsyncResult := v.isCallAsync(s, pkg)
2421
- if isCallAsyncResult {
2622
+ if v.isCallAsync(s, pkg) {
2422
2623
  hasAsync = true
2423
- callName := ""
2424
- if ident, ok := s.Fun.(*ast.Ident); ok {
2425
- callName = ident.Name
2426
- } else if sel, ok := s.Fun.(*ast.SelectorExpr); ok {
2427
- callName = sel.Sel.Name
2428
- }
2429
- asyncReasons = append(asyncReasons, fmt.Sprintf("async call: %s", callName))
2430
2624
  return false
2431
2625
  }
2432
2626
  }
@@ -2444,7 +2638,8 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
2444
2638
  // Direct function call
2445
2639
  if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
2446
2640
  if funcObj, ok := obj.(*types.Func); ok {
2447
- return v.isFunctionAsync(funcObj, pkg)
2641
+ result := v.isFunctionAsync(funcObj, pkg)
2642
+ return result
2448
2643
  }
2449
2644
  }
2450
2645
 
@@ -2467,14 +2662,16 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
2467
2662
  if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
2468
2663
  methodName := fun.Sel.Name
2469
2664
  // For interface method calls, check if the interface method is async
2470
- return v.analysis.IsInterfaceMethodAsync(interfaceType, methodName)
2665
+ result := v.analysis.IsInterfaceMethodAsync(interfaceType, methodName)
2666
+ return result
2471
2667
  }
2472
2668
  }
2473
2669
 
2474
2670
  // Method call on concrete objects
2475
2671
  if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
2476
2672
  if methodObj := selection.Obj(); methodObj != nil {
2477
- return v.isMethodAsyncFromSelection(fun, methodObj, pkg)
2673
+ result := v.isMethodAsyncFromSelection(fun, methodObj, pkg)
2674
+ return result
2478
2675
  }
2479
2676
  }
2480
2677
  }
@@ -2489,7 +2686,7 @@ func (v *analysisVisitor) isFunctionAsync(funcObj *types.Func, pkg *packages.Pac
2489
2686
  return v.analysis.IsMethodAsync(funcObj.Pkg().Path(), "", funcObj.Name())
2490
2687
  }
2491
2688
 
2492
- // Check internal method status
2689
+ // Check internal method status (should already be computed during analysis)
2493
2690
  methodKey := MethodKey{
2494
2691
  PackagePath: pkg.Types.Path(),
2495
2692
  ReceiverType: "",
@@ -2500,14 +2697,7 @@ func (v *analysisVisitor) isFunctionAsync(funcObj *types.Func, pkg *packages.Pac
2500
2697
  return status
2501
2698
  }
2502
2699
 
2503
- // Not analyzed yet, analyze now
2504
- if funcDecl := v.findFunctionDecl(funcObj.Name(), pkg); funcDecl != nil {
2505
- v.analyzeMethodAsync(funcDecl, pkg)
2506
- if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2507
- return status
2508
- }
2509
- }
2510
-
2700
+ // Not found - should have been analyzed during analyzeAllMethodsAsync
2511
2701
  return false
2512
2702
  }
2513
2703
 
@@ -2527,7 +2717,12 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
2527
2717
  }
2528
2718
  }
2529
2719
  case *ast.SelectorExpr:
2530
- // Field access (e.g., l.m.Lock())
2720
+ // Field access (e.g., l.m.Lock() or d.mu.Lock())
2721
+ if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
2722
+ receiverType = v.getTypeName(typeExpr)
2723
+ }
2724
+ default:
2725
+ // For other cases, try to get type directly
2531
2726
  if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
2532
2727
  receiverType = v.getTypeName(typeExpr)
2533
2728
  }
@@ -2540,7 +2735,6 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
2540
2735
  }
2541
2736
  }
2542
2737
 
2543
- // If no package path found, use current package
2544
2738
  if methodPkgPath == "" {
2545
2739
  methodPkgPath = pkg.Types.Path()
2546
2740
  }
@@ -2557,28 +2751,7 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
2557
2751
  return status
2558
2752
  }
2559
2753
 
2560
- // Check if this is a method in the same package we're currently analyzing
2561
- if methodPkgPath == v.pkg.Types.Path() {
2562
- // This is a method in the same package - we should analyze it if we haven't yet
2563
- if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), v.pkg); funcDecl != nil {
2564
- v.analyzeMethodAsync(funcDecl, v.pkg)
2565
- if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2566
- return status
2567
- }
2568
- }
2569
- } else {
2570
- // For methods in other packages that we're compiling together
2571
- if targetPkg := v.analysis.AllPackages[methodPkgPath]; targetPkg != nil {
2572
- // Try to analyze the method if we haven't already
2573
- if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), targetPkg); funcDecl != nil {
2574
- v.analyzeMethodAsync(funcDecl, targetPkg)
2575
- if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
2576
- return status
2577
- }
2578
- }
2579
- }
2580
- }
2581
-
2754
+ // Not found - should have been analyzed during analyzeAllMethodsAsync
2582
2755
  return false
2583
2756
  }
2584
2757
 
@@ -2618,22 +2791,6 @@ func (v *analysisVisitor) findMethodDecl(receiverType, methodName string, pkg *p
2618
2791
  return nil
2619
2792
  }
2620
2793
 
2621
- // checkExternalMethodMetadata checks if an external method is async based on pre-loaded metadata
2622
- func (v *analysisVisitor) checkExternalMethodMetadata(pkgPath, receiverType, methodName string) bool {
2623
- // Use MethodKey to check pre-loaded metadata in MethodAsyncStatus
2624
- key := MethodKey{
2625
- PackagePath: pkgPath,
2626
- ReceiverType: receiverType,
2627
- MethodName: methodName,
2628
- }
2629
-
2630
- if isAsync, exists := v.analysis.MethodAsyncStatus[key]; exists {
2631
- return isAsync
2632
- }
2633
-
2634
- return false
2635
- }
2636
-
2637
2794
  // IsLocalMethodAsync checks if a local method is async using pre-computed analysis
2638
2795
  func (a *Analysis) IsLocalMethodAsync(pkgPath, receiverType, methodName string) bool {
2639
2796
  methodKey := MethodKey{
@@ -2648,31 +2805,3 @@ func (a *Analysis) IsLocalMethodAsync(pkgPath, receiverType, methodName string)
2648
2805
 
2649
2806
  return false
2650
2807
  }
2651
-
2652
- // updateInterfaceImplementationAsyncStatus updates interface implementations with correct async status
2653
- // This runs after method async analysis is complete
2654
- func (v *analysisVisitor) updateInterfaceImplementationAsyncStatus() {
2655
- // Iterate through all tracked interface implementations and update their async status
2656
- for key, implementations := range v.analysis.InterfaceImplementations {
2657
- // Remove duplicates first
2658
- seenMethods := make(map[string]bool)
2659
- uniqueImplementations := []ImplementationInfo{}
2660
-
2661
- for _, impl := range implementations {
2662
- methodKey := impl.StructType.Obj().Name() + "." + key.MethodName
2663
- if !seenMethods[methodKey] {
2664
- seenMethods[methodKey] = true
2665
-
2666
- // Now that method async analysis is complete, get the correct async status
2667
- isAsync := v.analysis.IsAsyncFunc(impl.Method)
2668
-
2669
- // Update the implementation with the correct async status
2670
- impl.IsAsyncByFlow = isAsync
2671
- uniqueImplementations = append(uniqueImplementations, impl)
2672
- }
2673
- }
2674
-
2675
- // Store the updated implementations without duplicates
2676
- v.analysis.InterfaceImplementations[key] = uniqueImplementations
2677
- }
2678
- }