feat(errors): structured runtime errors — catch binds {kind, message, line} (#406)#468
Merged
Merged
Conversation
… line} (#406) BREAKING: catch now binds a {kind, message, line} dict for BUILT-IN runtime errors instead of the flat "Error line N: ..." string. kind is drawn from a closed, SPEC-enumerated vocabulary (undefined_name, type_mismatch, value, index_range, parse, io, limit, sandbox, interrupt, assert, internal) — discriminating error classes no longer means string matching (the silent-tolerance bug class). User-thrown values bind untouched, exactly as before. Uncaught stderr output is unchanged, so the EM corpus survives byte-for-byte. Mechanics: runtime_error(line, fmt) is renamed rt_error(kind, line, fmt) — the rename forces every raise site through classification; all 173 sites across vm.c/builtins.c/ext_store.c/ext_db.c/ext_http.c are bucketed. The catch dict is built lazily in vm_take_error_value (heap dict, owned children — no allocation on the uncaught path). Builtin raise sites (line 0) now stamp the VM's live line, fixing the "Error line 0:" wart. Also: - assert failures are ordinary catchable errors (kind assert); caught asserts no longer leak an "ASSERT FAIL" line to stderr - sandbox_run's result dict gains "error": {kind, message, line} when ok is 0 (the graded ladder can discriminate denial vs type vs parse) - embed API: eigs_last_error_kind() / eigs_last_error_line(); throw stamps kind "user" for host introspection only - SPEC.md Error handling rewritten with executable examples; DIAGNOSTICS.md gains the closed kind table; COMPARISON/EMBEDDING synced; examples/catching_by_kind.eigs added - suite: EM26–EM30 assert the new binding; ~75 test string-matches migrated to .message/.kind (user-throw sites deliberately left) Consumer audit (all 10 + EigenOS): zero breaking catch sites. Parity note for the v0.28.0 bump: ouroboros frontend.eigs (+ the iLambdaAi vendored copy) must mirror the dict binding for interpreter-raised errors; Tidepool tidepool.eigs:1067 render-error print becomes a dict repr (cosmetic). Closes #406 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #406
What
catchbinds a small dict{kind, message, line}for built-in runtime errors;kindcomes from a closed, SPEC-enumerated vocabulary. User-thrown values bind untouched. Nofinally, no exception hierarchy — the closed kind vocabulary is the same design instinct as the closed trajectory vocabulary. BREAKING (v0.28.0 train), no backcompat shims.The closed kind set
undefined_name·type_mismatch·value·index_range·parse·io·limit·sandbox·interrupt·assert·internal— table with semantics in docs/DIAGNOSTICS.md. Notably noaritykind: user fn calls pad with null by design, so builtin argument-contract violations aretype_mismatch.How
runtime_error(line, fmt)→rt_error(kind, line, fmt)— the rename forces every raise site through classification (an added-first-enum-param would have silently compiledrt_error(5, "msg")with the int line as kind). All 173 sites bucketed.vm_take_error_value; the uncaught path never allocates. Uncaught stderr output is byte-unchanged (EM1–EM25 untouched).Error line 0:wart.assertroutes throughrt_error(kindassert): catchable, and caught asserts no longer print to stderr.sandbox_runresult gains"error": {kind, message, line}onok: 0— the iLambdaAi graded ladder can discriminate sandbox denial vs type error vs bad descriptor without re-running.eigs_last_error_kind(),eigs_last_error_line().Verification
detect_leaks=1, leak tally 0make http/make lsp/make jit-smoke/make freestanding-check/ amalgamation +embed-smoke/tools/embed_stack_soak.shall greenmake full(db) not buildable locally (no libpq on this box — pre-existing); the ext_db.c diff is two mechanical call rewrites, release CI builds that variantConsumer audit (pre-landing, all 10 + EigenOS)
Zero breaking catch sites. No consumer string-matches builtin error text or discriminates via
type of e. Bump-time ledger for v0.28.0:print of f"Render error: {err}"output becomes a dict repr (cosmetic).message/.kind) is the template for any consumer that grows one laterFollow-up (separate, per the issue)
Lint warning on comparing
e.kindagainst a string outside the closed set — filing next.🤖 Generated with Claude Code