Skip to content

Commit 3ef2cf9

Browse files
committed
feat: implement comprehensive closure variable capture with two-phase approach
- Add statement preprocessor to analyze closures before transpilation - Generate clone statements at statement level for captured variables - Fix closure calling mechanism with proper dereferencing - Handle nested closures and complex capture scenarios correctly - Fix double-wrapping issue with function literals in assignments - Add heuristic filtering to prevent capturing package names/constants - Fix return statements to use renamed captured variables Results: 44/132 tests passing (33%), function_literals_closures test now passes
1 parent a43d65b commit 3ef2cf9

File tree

12 files changed

+582
-43
lines changed

12 files changed

+582
-43
lines changed

AGENTS.md

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,19 @@ The test script handles:
110110
- Limited stdlib support
111111
- No circular dependencies or build tags
112112

113-
## Recent Progress (2025-08-21)
113+
## Recent Progress (2025-08-22)
114+
115+
- **Comprehensive closure capture fix**: Implemented two-phase approach for proper closure variable capture
116+
- Created statement preprocessor (`stmt_preprocess.go`) to analyze closures before transpilation
117+
- Generates clone statements at statement level, before closures are created
118+
- Properly handles nested closures and complex capture scenarios
119+
- Fixed closure calling mechanism to properly dereference wrapped closures
120+
- Fixed double-wrapping issue with function literals in assignments
121+
- **Return statement improvements**: Variables in return statements now use renamed captures
122+
- **Heuristic-based capture filtering**: Prevents capturing package names and constants
123+
- All defer and goroutine tests maintained
124+
125+
Previous progress (2025-08-21):
114126

115127
- **Defer statements fully working**: Fixed variable capture and argument evaluation, LIFO execution order correct
116128
- Closures now respect pre-configured capture renames from defer handlers
@@ -119,13 +131,6 @@ The test script handles:
119131
- **Map iteration determinism**: Added sorting to ensure deterministic output for map iteration tests
120132
- **Printf format string handling**: Improved conversion of Go format strings to Rust equivalents
121133

122-
Previous progress (2025-08-15):
123-
124-
- **Closures fully working**: Fixed variable capture, proper unwrapping of return values, correct handling of range loop variables
125-
- **Defer statements improved**: Immediate argument evaluation for deferred closures, proper LIFO execution
126-
- **Basic interface{} support**: Empty interface with Box<dyn Any>, format_any helper for printing
127-
- **Deterministic output**: Fixed non-deterministic ordering in anonymous structs, promoted methods, and interfaces
128-
129134
## ✅ Type System Integration (COMPLETED)
130135

131136
The transpiler now uses `go/types` for accurate type information instead of brittle heuristics:
@@ -189,18 +194,22 @@ If `GetTypeInfo()` returns nil (shouldn't happen in normal operation):
189194

190195
### Closures
191196

192-
The closure variable capture is partially working but needs refinement for general closures:
197+
The general closure capture mechanism is now working through a two-phase approach (implemented 2025-08-22):
198+
199+
1.**Statement preprocessing** - Analyzes closures before transpilation
200+
2.**Clone generation at statement level** - Generates clones before the statement
201+
3.**Variable renaming** - Properly renames captured variables in closure bodies
202+
4.**Nested closures** - Handles closures within closures correctly
193203

194-
1. **Capture detection works** - We can identify which variables are captured
195-
2. **Renaming infrastructure exists** - Variables can be renamed in closure bodies
196-
3. **Clone generation needs work** - Clones need to be generated at statement level, not inline
197-
4. **Some complex cases fail** - Nested closures in expressions, etc.
204+
Remaining limitations:
198205

199-
The main challenge is that clones need to be generated before the statement containing the closure,
200-
not inside the closure expression itself. This requires statement-level analysis and transformation.
206+
- Some edge cases with variadic functions
207+
- Cross-file function variables need more work
208+
- Anonymous struct methods with closures
201209

202-
Note: Defer statements with closures now work correctly as of 2025-08-21, providing a model for how
203-
statement-level capture could work for other closure use cases.
210+
The solution uses a statement preprocessor (`stmt_preprocess.go`) that analyzes all closures
211+
in a statement, generates appropriate clone statements, and sets up variable renames that are
212+
respected during transpilation.
204213

205214
## Recent Progress (2025-08-21)
206215

go/expr.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,14 +1452,21 @@ func TranspileCall(out *strings.Builder, call *ast.CallExpr) {
14521452
out.WriteString(ToSnakeCase(ident.Name))
14531453
} else {
14541454
// Likely a closure variable - need to unwrap and call
1455-
out.WriteString("(")
1456-
out.WriteString(ident.Name)
1455+
// Check if this variable has been renamed (captured in closure)
1456+
varName := ident.Name
1457+
if currentCaptureRenames != nil {
1458+
if renamed, exists := currentCaptureRenames[ident.Name]; exists {
1459+
varName = renamed
1460+
}
1461+
}
1462+
out.WriteString("(*")
1463+
out.WriteString(varName)
14571464
WriteBorrowMethod(out, false)
14581465
out.WriteString(".as_ref().unwrap())")
14591466
}
14601467
} else {
14611468
// Complex expression for the function (e.g., function returning a function)
1462-
out.WriteString("(")
1469+
out.WriteString("(*")
14631470
TranspileExpression(out, call.Fun)
14641471
WriteBorrowMethod(out, false)
14651472
out.WriteString(".as_ref().unwrap())")

go/stmt.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ func TranspileStatement(out *strings.Builder, stmt ast.Stmt, fnType *ast.FuncTyp
8181
if stmt != nil && comments != nil && lastPos != nil {
8282
outputCommentsBeforePos(out, comments, fileSet, stmt.Pos(), indent, lastPos)
8383
}
84+
85+
// Preprocess the statement to find closures and generate clone statements
86+
// Skip defer statements as they handle captures themselves
87+
var captureInfo *CaptureInfo
88+
if _, isDefer := stmt.(*ast.DeferStmt); !isDefer && statementPreprocessor != nil {
89+
captureInfo = statementPreprocessor.PreprocessStatement(stmt, fnType)
90+
if captureInfo != nil && len(captureInfo.CapturedVars) > 0 {
91+
// Generate clone statements before the actual statement
92+
statementPreprocessor.GenerateCloneStatements(out, captureInfo)
93+
94+
// Set up capture renames for this statement
95+
oldCaptureRenames := currentCaptureRenames
96+
currentCaptureRenames = captureInfo.CapturedVars
97+
defer func() { currentCaptureRenames = oldCaptureRenames }()
98+
}
99+
}
100+
84101
switch s := stmt.(type) {
85102
case *ast.ExprStmt:
86103
TranspileExpression(out, s.X)
@@ -235,7 +252,14 @@ func TranspileStatement(out *strings.Builder, stmt ast.Stmt, fnType *ast.FuncTyp
235252

236253
if isWrappedVariable {
237254
// This is a wrapped variable - clone it to avoid move errors
238-
out.WriteString(ident.Name)
255+
// Check if this variable has been renamed (captured in closure)
256+
varName := ident.Name
257+
if currentCaptureRenames != nil {
258+
if renamed, exists := currentCaptureRenames[ident.Name]; exists {
259+
varName = renamed
260+
}
261+
}
262+
out.WriteString(varName)
239263
out.WriteString(".clone()")
240264
} else {
241265
// This needs wrapping (constants, literals, etc.)
@@ -875,6 +899,9 @@ func TranspileStatement(out *strings.Builder, stmt ast.Stmt, fnType *ast.FuncTyp
875899
} else if _, isCall := rhs.(*ast.CallExpr); isCall {
876900
// Function calls already return wrapped values, don't wrap again
877901
TranspileExpression(out, rhs)
902+
} else if _, isFuncLit := rhs.(*ast.FuncLit); isFuncLit {
903+
// Function literals are already wrapped by TranspileFuncLit
904+
TranspileExpression(out, rhs)
878905
} else if compositeLit, isCompositeLit := rhs.(*ast.CompositeLit); isCompositeLit {
879906
// Check if it's a struct literal vs array/slice/map literal
880907
isStructLiteral := false

0 commit comments

Comments
 (0)