@@ -2756,23 +2756,25 @@ func (v *analysisVisitor) analyzeAllMethodsAsync() {
27562756 // Topologically sort methods by their dependencies
27572757 sorted , cycles := v .topologicalSortMethods (methodCalls )
27582758
2759- // Mark methods in cycles - check if they contain async operations
2760- // We can't rely on the call graph for methods in cycles (circular dependency),
2761- // but we can still detect if they call async external methods
2762- //
2763- // We need to iterate multiple times because methods in cycles can call each other,
2764- // and we need to propagate async status until no changes occur
2759+ // Analyze methods in dependency order (dependencies analyzed before dependents)
2760+ for _ , methodKey := range sorted {
2761+ v .analyzeMethodAsyncTopological (methodKey , methodCalls [methodKey ])
2762+ }
2763+
2764+ // Analyze methods in cycles after the acyclic portion of the graph is known.
2765+ // This lets recursive methods observe async callees outside the cycle while
2766+ // still converging over recursive edges inside the cycle.
27652767 maxIterations := 10
27662768 for range maxIterations {
27672769 changed := false
27682770
27692771 for _ , methodKey := range cycles {
2770- // For methods in cycles, we need to check their body directly for async operations
27712772 pkg := v .analysis .AllPackages [methodKey .PackagePath ]
27722773 if pkg == nil && methodKey .PackagePath == v .pkg .Types .Path () {
27732774 pkg = v .pkg
27742775 }
27752776
2777+ isAsync := false
27762778 if pkg != nil {
27772779 var funcDecl * ast.FuncDecl
27782780 if methodKey .ReceiverType == "" {
@@ -2782,37 +2784,29 @@ func (v *analysisVisitor) analyzeAllMethodsAsync() {
27822784 }
27832785
27842786 if funcDecl != nil && funcDecl .Body != nil {
2785- // Check if the method contains async operations (including calls to async external methods)
2786- isAsync := v .containsAsyncOperationsComplete (funcDecl .Body , pkg )
2787-
2788- // Check if status changed
2789- if oldStatus , exists := v .analysis .MethodAsyncStatus [methodKey ]; ! exists || oldStatus != isAsync {
2790- changed = true
2787+ isAsync = v .containsAsyncOperationsComplete (funcDecl .Body , pkg )
2788+ if ! isAsync {
2789+ for _ , callee := range methodCalls [methodKey ] {
2790+ if calleeAsync , exists := v .analysis .MethodAsyncStatus [callee ]; exists && calleeAsync {
2791+ isAsync = true
2792+ break
2793+ }
2794+ }
27912795 }
2792-
2793- v .analysis .MethodAsyncStatus [methodKey ] = isAsync
2794- continue
27952796 }
27962797 }
27972798
2798- // Fallback: mark as sync if we can't analyze the body
2799- if _ , exists := v .analysis .MethodAsyncStatus [methodKey ]; ! exists {
2800- v .analysis .MethodAsyncStatus [methodKey ] = false
2799+ if oldStatus , exists := v .analysis .MethodAsyncStatus [methodKey ]; ! exists || oldStatus != isAsync {
28012800 changed = true
28022801 }
2802+ v .analysis .MethodAsyncStatus [methodKey ] = isAsync
28032803 }
28042804
2805- // If no changes in this iteration, we're done
28062805 if ! changed {
28072806 break
28082807 }
28092808 }
28102809
2811- // Analyze methods in dependency order (dependencies analyzed before dependents)
2812- for _ , methodKey := range sorted {
2813- v .analyzeMethodAsyncTopological (methodKey , methodCalls [methodKey ])
2814- }
2815-
28162810 // Track async-returning variables BEFORE analyzing function literals
28172811 // This detects variables assigned from higher-order functions with async function literal args
28182812 // e.g., indirect := sync.OnceValue(asyncFunc)
@@ -3259,6 +3253,11 @@ func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *pa
32593253 if n == nil {
32603254 return false
32613255 }
3256+ if n != node {
3257+ if _ , ok := n .(* ast.FuncLit ); ok {
3258+ return false
3259+ }
3260+ }
32623261
32633262 switch s := n .(type ) {
32643263 case * ast.SendStmt :
0 commit comments