Skip to content

Commit 9d0ee37

Browse files
committed
fix: nested_async_method_-value and named_struct_underlying_methodset
Signed-off-by: Christian Stewart <christian@aperture.us>
1 parent d8ffd91 commit 9d0ee37

15 files changed

Lines changed: 560 additions & 84 deletions

File tree

compiler/analysis.go

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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:

compiler/decl.go

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package compiler
22

33
import (
4-
"bytes"
54
"fmt"
65
"go/ast"
76
"go/token"
@@ -678,14 +677,6 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
678677
}
679678
}
680679

681-
if !isAsync {
682-
bodyNeedsAsync, err := c.funcBodyNeedsAsync(decl, true)
683-
if err != nil {
684-
return false, err
685-
}
686-
isAsync = bodyNeedsAsync
687-
}
688-
689680
// Methods are typically public in the TS output
690681
c.tsw.WriteLiterally("public ")
691682

@@ -753,31 +744,6 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
753744
return isAsync, nil
754745
}
755746

756-
// funcBodyNeedsAsync checks whether emitting a function body would generate await.
757-
func (c *GoToTSCompiler) funcBodyNeedsAsync(decl *ast.FuncDecl, isMethod bool) (bool, error) {
758-
if decl.Body == nil {
759-
return false, nil
760-
}
761-
762-
var body strings.Builder
763-
writer := NewTSCodeWriter(&body)
764-
tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis, c.currentFilePath)
765-
766-
if isMethod {
767-
if err := tempCompiler.writeMethodBodyWithReceiverBinding(decl, "this"); err != nil {
768-
return false, err
769-
}
770-
} else {
771-
for _, stmt := range decl.Body.List {
772-
if err := tempCompiler.WriteStmt(stmt); err != nil {
773-
return false, err
774-
}
775-
}
776-
}
777-
778-
return bytes.Contains([]byte(body.String()), []byte("await")), nil
779-
}
780-
781747
// writeMethodBodyWithReceiverBinding writes the method body with optional receiver binding
782748
// receiverTarget should be "this" for struct methods or "this._value" for named type methods
783749
func (c *GoToTSCompiler) writeMethodBodyWithReceiverBinding(decl *ast.FuncDecl, receiverTarget string) error {

0 commit comments

Comments
 (0)