feat: implement resumable effect handlers

Add support for the `resume(value)` expression in effect handler
bodies. When resume is called, the value becomes the return value
of the effect operation, allowing handlers to provide values back
to the calling code.

Implementation:
- Add Resume(Value) variant to EvalResult
- Add in_handler_depth tracking to Interpreter
- Update Expr::Resume evaluation to return Resume when in handler
- Handle Resume results in handle_effect to use as return value
- Add 2 tests for resumable handlers

Example usage:
```lux
handler prettyLogger: Logger {
    fn log(level, msg) = {
        Console.print("[" + level + "] " + msg)
        resume(())  // Return Unit to the call site
    }
}
```

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 09:54:51 -05:00
parent 52ad5f8781
commit 9511957076
3 changed files with 127 additions and 5 deletions

View File

@@ -1292,6 +1292,47 @@ c")"#;
// Timestamp should be a reasonable Unix time in milliseconds (after 2020)
assert!(timestamp > 1577836800000, "Timestamp should be after 2020");
}
#[test]
fn test_resumable_handler() {
// Test that resume works in handler bodies
let source = r#"
effect Counter {
fn increment(): Int
}
fn useCounter(): Int with {Counter} = {
let a = Counter.increment()
let b = Counter.increment()
a + b
}
handler countingHandler: Counter {
fn increment() = resume(10)
}
let result = run useCounter() with {
Counter = countingHandler
}
"#;
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
// Each increment returns 10, so a + b = 10 + 10 = 20
assert_eq!(result, "20");
}
#[test]
fn test_resume_outside_handler_fails() {
// Resume outside handler should fail at runtime
let source = r#"
fn bad(): Int = resume(42)
let result = bad()
"#;
let result = run_with_effects(source, Value::Unit, Value::Unit);
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(err_msg.contains("outside") || err_msg.contains("Resume"),
"Error should mention resume outside handler: {}", err_msg);
}
}
// Diagnostic rendering tests