Data-first HO forms, fix plan pages, aser error handling (1080/1080)
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled

Evaluator: data-first higher-order forms — ho-swap-args auto-detects
(map coll fn) vs (map fn coll), both work. Threading + HO: (-> data
(map fn)) dispatches through CEK HO machinery via quoted-value splice.
17 new tests in test-cek-advanced.sx.

Fix plan pages: add mother-language, isolated-evaluator, rust-wasm-host
to page-functions.sx plan() — were in defpage but missing from URL router.

Aser error handling: pages.py now catches EvalError separately, renders
visible error banner instead of silently sending empty content. All
except blocks include traceback in logs.

Scope primitives: register collect!/collected/clear-collected!/emitted/
emit!/context in shared/sx/primitives.py so hand-written _aser can
resolve them (fixes ~cssx/flush expansion failure).

New test file: shared/sx/tests/test_aser_errors.py — 19 pytest tests
for error propagation through all aser control flow forms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 18:05:00 +00:00
parent bdbf594bc8
commit 3a268e7277
10 changed files with 615 additions and 51 deletions

View File

@@ -344,3 +344,82 @@
(deftest "scope pops correctly after body"
(assert-equal "outer"
(render-sx "(scope \"sc-pop\" :value \"outer\" (scope \"sc-pop\" :value \"inner\" \"ignore\") (context \"sc-pop\"))"))))
;; --------------------------------------------------------------------------
;; Error propagation — errors in aser control flow must throw, not silently
;; produce wrong output or fall through to :else branches.
;; --------------------------------------------------------------------------
(defsuite "aser-error-propagation"
;; --- case: matched branch errors must throw, not fall through to :else ---
(deftest "case — error in matched branch throws, not falls through"
;; If the matched case body references an undefined symbol, the aser must
;; throw an error — NOT silently skip to :else.
(assert-throws
(fn () (render-sx "(case \"x\" \"x\" undefined-symbol-xyz :else \"fallback\")"))))
(deftest "case — :else body error also throws"
(assert-throws
(fn () (render-sx "(case \"no-match\" \"x\" \"ok\" :else undefined-symbol-xyz)"))))
(deftest "case — matched branch with nested error throws"
;; Error inside a tag within the matched body must propagate.
(assert-throws
(fn () (render-sx "(case \"a\" \"a\" (div (p undefined-sym-abc)) :else (p \"index\"))"))))
;; --- cond: matched branch errors must throw ---
(deftest "cond — error in matched branch throws"
(assert-throws
(fn () (render-sx "(cond true undefined-cond-sym :else \"fallback\")"))))
(deftest "cond — error in :else branch throws"
(assert-throws
(fn () (render-sx "(cond false \"skip\" :else undefined-cond-sym)"))))
;; --- if/when: body errors must throw ---
(deftest "if — error in true branch throws"
(assert-throws
(fn () (render-sx "(if true undefined-if-sym \"fallback\")"))))
(deftest "when — error in body throws"
(assert-throws
(fn () (render-sx "(when true undefined-when-sym)"))))
;; --- let: binding or body errors must throw ---
(deftest "let — error in binding throws"
(assert-throws
(fn () (render-sx "(let ((x undefined-let-sym)) (p x))"))))
(deftest "let — error in body throws"
(assert-throws
(fn () (render-sx "(let ((x 1)) (p undefined-let-body-sym))"))))
;; --- begin/do: body errors must throw ---
(deftest "do — error in body throws"
(assert-throws
(fn () (render-sx "(do \"ok\" undefined-do-sym)"))))
;; --- component expansion inside case: the production bug ---
;; --- sync aser serializes components without expansion ---
(deftest "case — component in matched branch serializes unexpanded"
;; Sync aser serializes component calls as SX wire format.
;; Expansion only happens in async path with expand-components.
(assert-equal "(~broken :title \"test\")"
(render-sx
"(do (defcomp ~broken (&key title) (div (p title) (p no-such-helper)))
(case \"slug\" \"slug\" (~broken :title \"test\") :else \"index\"))")))
(deftest "case — unmatched falls through to :else correctly"
(assert-equal "index"
(render-sx
"(do (defcomp ~page (&key x) (div x))
(case \"miss\" \"hit\" (~page :x \"found\") :else \"index\"))"))))