sx: step 8 — non-exhaustive match warnings

Emit a warning when a `match` expression on an ADT value misses one
or more constructors and lacks an `else`/`_` clause. Behaviour is
non-fatal — the match still runs, the warning goes to stderr.

- spec/evaluator.sx: helpers `match-clause-is-else?`, `match-clause-ctor-name`,
  `match-warn-non-exhaustive`, `match-check-exhaustiveness`. The latter
  reads the `*adt-registry*` (already populated by `define-type`),
  collects constructor patterns from clauses, and dedupes via an
  `*adt-warned*` env-bound dict so each (type, missing-set) warns once.
  Wired into `step-sf-match` via a `do` block before clause dispatch.

- hosts/javascript/platform.py: `host-warn` primitive (`console.warn`)
  + matching `hostWarn` js-id helper so the JS-transpiled spec code
  can call it directly. Spec code reaches JS via `sx_build target=js`.

- hosts/ocaml/lib/sx_runtime.ml + sx_primitives.ml: `host-warn` runtime
  helper (`prerr_endline`) and registered primitive.

- hosts/ocaml/lib/sx_ref.ml: HAND-PATCHED. `step_sf_match` now calls
  a hand-written `match_check_exhaustiveness` that handles both
  `AdtValue` and back-compat dict-shape ADT values. The OCaml side
  is *not* retranspiled because regenerating sx_ref.ml drops
  several preamble fixes (seq_to_list, string->symbol mangling,
  empty-dict literal bug). Future retranspile must reapply this patch.

- spec/tests/test-adt.sx: 5 new tests covering exhaustive,
  non-exhaustive (warning is non-fatal), `else` suppression,
  partial coverage with one missing constructor, and `_` wildcard
  suppression. Tests assert return values only — warnings go to
  stderr and are not captured.

Warning format: `[sx] match: non-exhaustive — TypeName: missing Ctor1, Ctor2`
Both hosts emit identical messages.

Tests: OCaml 4540 → 4545 (+5), JS 2586 → 2591 (+5). Zero regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 00:13:41 +00:00
parent 7b050fb217
commit 6d39111992
8 changed files with 281 additions and 15 deletions

View File

@@ -142,6 +142,18 @@ zero regressions (OCaml 4532→4540, JS 2578→2586).
On first non-exhaustive `match` evaluation: `console.warn("[sx] match: non-exhaustive …")`.
No error — warning only.
**Outcome:** `host-warn` primitive added on both hosts (OCaml `prerr_endline`,
JS `console.warn`). Spec-level helpers `match-clause-is-else?`,
`match-clause-ctor-name`, `match-warn-non-exhaustive`,
`match-check-exhaustiveness` added in `spec/evaluator.sx` and
called from `step-sf-match`. `*adt-warned*` env-bound dict used to
dedupe warnings per (type, missing-set). The OCaml `step_sf_match`
in `hosts/ocaml/lib/sx_ref.ml` was hand-patched (not retranspiled)
because `sx_ref.ml` retranspilation drops several preamble fixes;
the spec changes still flow to JS via `sx_build target="js"`. Both
hosts emit identical warnings (e.g. `[sx] match: non-exhaustive — Maybe: missing Nothing`).
5 new tests added. OCaml: 4540 → 4545. JS: 2586 → 2591. Zero regressions.
---
## Phase 4 — Plugin / extension system
@@ -202,7 +214,7 @@ these when operands are known numbers/lists.
| 5 — OCaml AdtValue + define-type + match | [x] | 1f49242a |
| 6 — JS AdtValue + define-type + match | [x] | fc8a3916 |
| 7 — nested patterns | [x] | 0679edf5 |
| 8 — exhaustiveness warnings | [ ] | |
| 8 — exhaustiveness warnings | [x] | (pending) |
| 9 — parser feature registry | [ ] | — |
| 10 — compiler + as converter registry | [ ] | — |
| 11 — plugin migration + worker | [ ] | — |