;; Letrec + perform/resume — tree-walk paths regression ;; ;; Before the fix: cek-run raised "IO suspension in non-IO context" when ;; a perform fired inside any tree-walked eval_expr path: ;; - sf_letrec init exprs / non-last body exprs (the original bug) ;; - sf_dynamic_wind, sf_scope, sf_provide bodies ;; - expand_macro body, qq_expand unquote ;; ;; After the fix: cek-run invokes _cek_io_suspend_hook which (in this test ;; runner) resolves the IO and returns the result, allowing evaluation to ;; complete successfully. End-to-end: the expression returns a value ;; instead of throwing the swallowed-suspension error. ;; ;; The test runner's hook resolves IO requests via resolve_io (mock). ;; In production browser, the hook raises VmSuspended → JS resume callback. (defsuite "letrec-resume-treewalk-init" (deftest "perform in letrec init expr — evaluates without error" (let ((result (eval-expr-cek (quote (letrec ((x (perform {:op "request-arg" :args (list "k")}))) (or x "default"))) (make-env)))) (assert (or (string? result) (nil? result))))) (deftest "perform in letrec non-last body — evaluates without error" (let ((result (eval-expr-cek (quote (letrec ((x 42)) (perform {:op "request-arg" :args (list "k")}) (+ x 1))) (make-env)))) (assert= result 43))) (deftest "letrec init perform result reachable — no nil sibling" (let ((result (eval-expr-cek (quote (letrec ((x 99) (y (fn () x))) (perform {:op "request-arg" :args (list "k")}) (y))) (make-env)))) (assert= result 99)))) (defsuite "letrec-resume-treewalk-bodies" (deftest "perform inside scope body — evaluates without error" (let ((result (eval-expr-cek (quote (scope "test-scope" :value 1 (perform {:op "request-arg" :args (list "k")}) "scope-done")) (make-env)))) (assert= result "scope-done"))) (deftest "perform inside provide body — evaluates without error" (let ((result (eval-expr-cek (quote (provide "test-key" 7 (perform {:op "request-arg" :args (list "k")}) "provide-done")) (make-env)))) (assert= result "provide-done")))) (defsuite "letrec-resume-treewalk-recursive" (deftest "letrec sibling fn called after perform/resume — sibling not nil" (let ((result (eval-expr-cek (quote (letrec ((sib "from-sibling") (suspending (fn () (do (perform {:op "request-arg" :args (list "k")}) sib)))) (suspending))) (make-env)))) (assert= result "from-sibling"))) (deftest "letrec — suspending fn calls sibling fn after own perform" (let ((result (eval-expr-cek (quote (letrec ((wait-and-call (fn () (do (perform {:op "request-arg" :args (list "k")}) (other)))) (other (fn () "called-after-resume"))) (wait-and-call))) (make-env)))) (assert= result "called-after-resume"))))