Skip to content

Commit bf5d132

Browse files
committed
defer statements
Signed-off-by: Tyler Laprade <[email protected]>
1 parent bd07cae commit bf5d132

File tree

11 files changed

+44
-29
lines changed

11 files changed

+44
-29
lines changed

AGENTS.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,12 @@ The test script handles:
112112

113113
## Recent Progress (2025-08-21)
114114

115+
- **Defer statements fully working**: Fixed variable capture and argument evaluation, LIFO execution order correct
116+
- Closures now respect pre-configured capture renames from defer handlers
117+
- Defer arguments capture VALUES at defer time, not references that could change
115118
- **Return statement fixes**: Fixed "cannot move out of mutable reference" errors by properly cloning wrapped values in return statements instead of unwrapping/re-wrapping
116119
- **Map iteration determinism**: Added sorting to ensure deterministic output for map iteration tests
117120
- **Printf format string handling**: Improved conversion of Go format strings to Rust equivalents
118-
- **Test suite progress**: 40/132 tests passing (30%), up from 37/132
119121

120122
Previous progress (2025-08-15):
121123

@@ -187,16 +189,19 @@ If `GetTypeInfo()` returns nil (shouldn't happen in normal operation):
187189

188190
### Closures
189191

190-
The closure variable capture is partially working but needs refinement:
192+
The closure variable capture is partially working but needs refinement for general closures:
191193

192194
1. **Capture detection works** - We can identify which variables are captured
193195
2. **Renaming infrastructure exists** - Variables can be renamed in closure bodies
194196
3. **Clone generation needs work** - Clones need to be generated at statement level, not inline
195-
4. **Complex cases fail** - Nested closures, defer with captures, etc.
197+
4. **Some complex cases fail** - Nested closures in expressions, etc.
196198

197199
The main challenge is that clones need to be generated before the statement containing the closure,
198200
not inside the closure expression itself. This requires statement-level analysis and transformation.
199201

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.
204+
200205
## Recent Progress (2025-08-21)
201206

202207
### ✅ Smart Concurrency-Based Optimization (COMPLETED)
@@ -213,11 +218,3 @@ Key components:
213218
- `WriteWrapperPrefix/Suffix()`: Uses appropriate wrapper based on concurrency
214219
- `WriteBorrowMethod()`: Uses `.borrow()` or `.lock().unwrap()` as needed
215220
- Helper functions adapt to concurrency (format_map, format_slice)
216-
217-
### Previous Progress (2025-08-15)
218-
219-
- **Closures fully working**: Fixed variable capture, proper unwrapping of return values
220-
- **Defer statements improved**: Immediate argument evaluation, proper LIFO execution
221-
- **Basic interface{} support**: Empty interface with Box<dyn Any>
222-
- **Deterministic output**: Fixed non-deterministic ordering issues
223-
- **Type system integration**: Full go/types integration for accurate type information

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ For unimplemented features, the transpiler generates TODO comments:
110110
| **`default` - Default clauses** | |
111111
| └ Switch default ||
112112
| └ Select default ||
113-
| **`defer` - Defer statements** | |
113+
| **`defer` - Defer statements** | |
114114
| **`else` - Else clauses** ||
115115
| **`fallthrough` - Fallthrough statements** ||
116116
| **`for` - For loops** | |

ROADMAP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ Method receivers (value and pointer), multiple returns, method calls
2626
- 🚧 Struct embedding - method promotion working, field promotion needs wrapper handling fixes
2727
- 🚧 Anonymous structs - partial support (type generation works, literals need fixing)
2828

29-
### 📋 Phase 5: Core Language Features (50% Complete)
29+
### 📋 Phase 5: Core Language Features (60% Complete)
3030

3131
- ✅ Basic constants - simple const declarations working
3232
- 🚧 Complex constants and iota - expressions, iota patterns need work
3333
- ✅ Closures and function literals - fully working with proper variable capture
34-
- 🚧 Defer statements - basic LIFO execution working, some edge cases remain
34+
- Defer statements - fully working with proper LIFO execution and variable capture
3535
- ❌ Panic and recover - not implemented
3636
- 🚧 Interfaces - empty interface{} support added, named interfaces incomplete
3737

go/expr.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -920,9 +920,20 @@ func TranspileFuncLit(out *strings.Builder, funcLit *ast.FuncLit) {
920920
// The clones need to be generated at the statement level
921921
captureRenames := make(map[string]string)
922922
for varName := range captured {
923-
// For now, just use the original names
924-
// A proper implementation would handle this at statement level
925-
captureRenames[varName] = varName
923+
// Check if we already have renames set up (e.g., from defer)
924+
// This allows statement-level handlers to pre-configure renames
925+
if currentCaptureRenames != nil {
926+
if existingRename, exists := currentCaptureRenames[varName]; exists && existingRename != "" {
927+
// Use the existing rename
928+
captureRenames[varName] = existingRename
929+
} else {
930+
// No existing rename for this variable, use identity
931+
captureRenames[varName] = varName
932+
}
933+
} else {
934+
// No existing renames at all, use identity
935+
captureRenames[varName] = varName
936+
}
926937
}
927938

928939
// Store current capture renames for nested transpilation

go/stmt.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,13 +1502,20 @@ func TranspileStatement(out *strings.Builder, stmt ast.Stmt, fnType *ast.FuncTyp
15021502
out.WriteString(" = ")
15031503

15041504
// Check if argument needs wrapping
1505+
// For defer arguments, we need to capture the VALUE at this moment,
1506+
// not a reference that could change later
15051507
if ident, ok := arg.(*ast.Ident); ok && ident.Name != "nil" && ident.Name != "_" {
15061508
// Check if this is a variable (not a constant)
15071509
if _, isRangeVar := rangeLoopVars[ident.Name]; !isRangeVar {
15081510
if _, isLocalConst := localConstants[ident.Name]; !isLocalConst {
1509-
// It's a variable, clone it
1511+
// It's a variable - capture its current value, not the reference
1512+
// This ensures each defer gets the value at the time of deferring
1513+
WriteWrapperPrefix(out)
1514+
out.WriteString("(*")
15101515
out.WriteString(ident.Name)
1511-
out.WriteString(".clone()")
1516+
WriteBorrowMethod(out, false)
1517+
out.WriteString(".as_ref().unwrap()).clone()")
1518+
WriteWrapperSuffix(out)
15121519
} else {
15131520
// It's a constant, wrap it
15141521
WriteWrapperPrefix(out)

tests.bats

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ run_xfail_test() {
299299
run_test "tests/constants_basic"
300300
}
301301

302+
@test "defer_statements" {
303+
run_test "tests/defer_statements"
304+
}
305+
302306
@test "error_simple" {
303307
run_test "tests/error_simple"
304308
}
@@ -507,10 +511,6 @@ run_xfail_test() {
507511
run_xfail_test "tests/XFAIL/crypto_hash"
508512
}
509513

510-
@test "XFAIL: defer_statements" {
511-
run_xfail_test "tests/XFAIL/defer_statements"
512-
}
513-
514514
@test "XFAIL: embedded_method_promotion" {
515515
run_xfail_test "tests/XFAIL/embedded_method_promotion"
516516
}

tests/XFAIL/panic_recover/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ pub fn safe_divide(a: Rc<RefCell<Option<f64>>>, b: Rc<RefCell<Option<f64>>>) ->
3333
(Rc::new(RefCell::new(Some(Box::new(move || {
3434
let mut r = Rc::new(RefCell::new(None))::<String>));
3535
if (*r.borrow()).is_some() {
36-
{ let new_val = Rc::new(RefCell::new(Some(Some(Box::new(format!("panic occurred: {}", (*r.borrow_mut().as_mut().unwrap()))) as Box<dyn Error + Send + Sync>))); *err.borrow_mut() = Some(new_val); };
37-
{ let new_val = 0; *result.borrow_mut() = Some(new_val); };
36+
{ let new_val = Rc::new(RefCell::new(Some(Some(Box::new(format!("panic occurred: {}", (*r.borrow_mut().as_mut().unwrap()))) as Box<dyn Error + Send + Sync>))); *err_defer_captured.borrow_mut() = Some(new_val); };
37+
{ let new_val = 0; *result_defer_captured.borrow_mut() = Some(new_val); };
3838
}
3939
}) as Box<dyn Fn() -> ()>))).borrow().as_ref().unwrap())();
4040
}));
@@ -68,8 +68,8 @@ pub fn process_slice(slice: Rc<RefCell<Option<Vec<i32>>>>, index: Rc<RefCell<Opt
6868
(Rc::new(RefCell::new(Some(Box::new(move || {
6969
let mut r = Rc::new(RefCell::new(None))::<String>));
7070
if (*r.borrow()).is_some() {
71-
{ let new_val = Rc::new(RefCell::new(Some(Some(Box::new(format!("index out of bounds: {}", (*r.borrow_mut().as_mut().unwrap()))) as Box<dyn Error + Send + Sync>))); *err.borrow_mut() = Some(new_val); };
72-
{ let new_val = -1; *value.borrow_mut() = Some(new_val); };
71+
{ let new_val = Rc::new(RefCell::new(Some(Some(Box::new(format!("index out of bounds: {}", (*r.borrow_mut().as_mut().unwrap()))) as Box<dyn Error + Send + Sync>))); *err_defer_captured.borrow_mut() = Some(new_val); };
72+
{ let new_val = -1; *value_defer_captured.borrow_mut() = Some(new_val); };
7373
}
7474
}) as Box<dyn Fn() -> ()>))).borrow().as_ref().unwrap())();
7575
}));
File renamed without changes.

0 commit comments

Comments
 (0)