diff --git a/lib/haskell/tests/errors.sx b/lib/haskell/tests/errors.sx new file mode 100644 index 00000000..f8cd5623 --- /dev/null +++ b/lib/haskell/tests/errors.sx @@ -0,0 +1,99 @@ +;; errors.sx — Phase 9 error / undefined / partial-fn coverage via hk-test-error. + +;; ── error builtin ──────────────────────────────────────────── +(define + hk-as-list + (fn + (xs) + (cond + ((and (list? xs) (= (first xs) "[]")) (list)) + ((and (list? xs) (= (first xs) ":")) + (cons (nth xs 1) (hk-as-list (nth xs 2)))) + (:else xs)))) + +(hk-test-error + "error: raises with literal message" + (fn () (hk-deep-force (hk-run "main = error \"boom\""))) + "hk-error: boom") + +(hk-test-error + "error: raises with computed message" + (fn () (hk-deep-force (hk-run "main = error (\"oops: \" ++ show 42)"))) + "hk-error: oops: 42") + +;; ── undefined ──────────────────────────────────────────────── +(hk-test-error + "error: nested in if branch (only fires when forced)" + (fn + () + (hk-deep-force (hk-run "main = if 1 == 1 then error \"taken\" else 0"))) + "taken") + +(hk-test-error + "undefined: raises Prelude.undefined" + (fn () (hk-deep-force (hk-run "main = undefined"))) + "Prelude.undefined") + +;; The non-strict path: undefined doesn't fire when not forced. +(hk-test-error + "undefined: forced via arithmetic" + (fn () (hk-deep-force (hk-run "main = undefined + 1"))) + "Prelude.undefined") + +;; ── partial functions ─────────────────────────────────────── +(hk-test + "undefined: lazy, not forced when discarded" + (hk-deep-force (hk-run "main = let _ = undefined in 5")) + 5) + +(hk-test-error + "head []: raises Prelude.head: empty list" + (fn () (hk-deep-force (hk-run "main = head []"))) + "Prelude.head: empty list") + +(hk-test-error + "tail []: raises Prelude.tail: empty list" + (fn () (hk-deep-force (hk-run "main = tail []"))) + "Prelude.tail: empty list") + +;; head and tail still work on non-empty lists. +(hk-test-error + "fromJust Nothing: raises Maybe.fromJust: Nothing" + (fn () (hk-deep-force (hk-run "main = fromJust Nothing"))) + "Maybe.fromJust: Nothing") + +(hk-test + "head [42]: still works" + (hk-deep-force (hk-run "main = head [42]")) + 42) + +;; ── error in IO context ───────────────────────────────────── +(hk-test + "tail [1,2,3]: still works" + (hk-as-list (hk-deep-force (hk-run "main = tail [1,2,3]"))) + (list 2 3)) + +(hk-test + "hk-run-io: error in main lands in io-lines" + (let + ((lines (hk-run-io "main = error \"caught here\""))) + (>= (index-of (str lines) "caught here") 0)) + true) + +;; ── hk-test-error helper itself ───────────────────────────── +(hk-test + "hk-run-io: putStrLn before error preserves earlier output" + (let + ((lines (hk-run-io "main = do { putStrLn \"first\"; error \"died\"; putStrLn \"never\" }"))) + (and + (>= (index-of (str lines) "first") 0) + (>= (index-of (str lines) "died") 0))) + true) + +;; hk-as-list helper for converting a forced Haskell cons into an SX list. +(hk-test-error + "hk-test-error: matches partial substring inside wrapped exception" + (fn () (hk-deep-force (hk-run "main = error \"unique-marker-xyz\""))) + "unique-marker-xyz") + +{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail} diff --git a/plans/haskell-completeness.md b/plans/haskell-completeness.md index b0f972ee..c33ec445 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -136,7 +136,7 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. - [x] `hk-test-error` helper in `testlib.sx`: `(hk-test-error "desc" thunk expected-substring)` — asserts the thunk raises an `hk-error` whose message contains the given substring. -- [ ] Tests in `lib/haskell/tests/errors.sx` (≥ 10 tests: error message +- [x] Tests in `lib/haskell/tests/errors.sx` (≥ 10 tests: error message content, undefined, head/tail/fromJust on bad input, `hk-test-error` helper). - [ ] Conformance programs: - `partial.hs` — exercises `head []`, `tail []`, `fromJust Nothing` caught @@ -293,6 +293,14 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. _Newest first._ +**2026-05-07** — Phase 9 `tests/errors.sx` (14/14): +- New file with 14 tests covering: error w/ literal + computed message; error + in `if` branch (laziness boundary); undefined via direct + forcing-via- + arithmetic + lazy-discard; partial functions head/tail/fromJust; head/tail + still working on non-empty input; hk-run-io's caught error landing in + io-lines; putStrLn-before-error preserving prior output; hk-test-error + substring match. Spec called for ≥10. + **2026-05-07** — Phase 9 `hk-test-error` helper in testlib.sx: - New 0-arity-thunk-based assertion: `(hk-test-error name thunk substr)` — evaluates `(thunk)`, expects an exception, checks `index-of` for the given