semantifold 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +1 -0
  2. package/build/src/backends/effects.js +3 -2
  3. package/build/src/backends/java.js +33 -14
  4. package/build/src/backends/javascript.js +83 -18
  5. package/build/src/backends/php.js +33 -14
  6. package/build/src/backends/ruby.js +37 -12
  7. package/build/src/backends/shared.d.ts.map +1 -1
  8. package/build/src/backends/shared.js +91 -36
  9. package/build/src/backends/typescript.js +83 -18
  10. package/build/src/frontends/java.d.ts +8 -0
  11. package/build/src/frontends/java.d.ts.map +1 -1
  12. package/build/src/frontends/java.js +77 -33
  13. package/build/src/frontends/javascript-typescript.d.ts +2 -0
  14. package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
  15. package/build/src/frontends/javascript-typescript.js +118 -37
  16. package/build/src/frontends/php.d.ts +8 -0
  17. package/build/src/frontends/php.d.ts.map +1 -1
  18. package/build/src/frontends/php.js +68 -29
  19. package/build/src/frontends/ruby.d.ts +3 -0
  20. package/build/src/frontends/ruby.d.ts.map +1 -1
  21. package/build/src/frontends/ruby.js +103 -30
  22. package/build/src/semantic/effects.d.ts.map +1 -1
  23. package/build/src/semantic/effects.js +18 -6
  24. package/build/src/semantic/lifetime.js +28 -4
  25. package/build/src/semantic/provenance.d.ts.map +1 -1
  26. package/build/src/semantic/provenance.js +26 -7
  27. package/build/src/semantic/types.d.ts +76 -16
  28. package/build/src/semantic/types.d.ts.map +1 -1
  29. package/build/src/semantic/types.js +23 -7
  30. package/build/src/semantic/validate.d.ts.map +1 -1
  31. package/build/src/semantic/validate.js +105 -34
  32. package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
  33. package/docs/adr-numeric-tower.md +156 -0
  34. package/docs/architecture.md +1 -1
  35. package/docs/language-support.md +7 -7
  36. package/docs/testing.md +5 -3
  37. package/package.json +1 -1
  38. package/src/backends/effects.js +2 -1
  39. package/src/backends/java.js +33 -14
  40. package/src/backends/javascript.js +83 -18
  41. package/src/backends/php.js +33 -14
  42. package/src/backends/ruby.js +36 -12
  43. package/src/backends/shared.js +88 -41
  44. package/src/backends/typescript.js +83 -18
  45. package/src/frontends/java.js +82 -33
  46. package/src/frontends/javascript-typescript.js +130 -38
  47. package/src/frontends/php.js +68 -27
  48. package/src/frontends/ruby.js +107 -28
  49. package/src/semantic/effects.js +17 -5
  50. package/src/semantic/lifetime.js +29 -3
  51. package/src/semantic/provenance.js +27 -12
  52. package/src/semantic/types.js +24 -6
  53. package/src/semantic/validate.js +104 -35
@@ -0,0 +1,55 @@
1
+ # ADR: Typed error handling extension — ordered catches, finally/ensure, and rethrow
2
+
3
+ Date: 2026-10-26
4
+ Task: [011 — Typed errors and handling](../../todo/011-typed-errors-and-handling.md) completion (STOE-62)
5
+ Status: Accepted for the original-five cohort (PHP, Ruby, JavaScript/JSDoc, TypeScript, Java)
6
+
7
+ ## Context
8
+
9
+ Task 011 delivered exactly one nominal catch per `TryStatement` and explicitly rejected `finally`/`ensure`, multiple catches, retry/rethrow, and checked-error effects. Every cohort language has had multi-handler and completion-block syntax from its first release, so the delivered subset could not express ordinary idiomatic error handling. This ADR records the extension decisions and the boundaries that stay closed.
10
+
11
+ ## Decision 1 — `TryStatement` owns an ordered handler list plus one optional completion block
12
+
13
+ `TryStatement` now carries `handlers: CatchHandler[]` and an optional `finallyBody: Block`. `CatchHandler` is a first-class semantic node (`catchType`, immutable `catchBinding`, `catchBody`) and joins the `SemanticNode` union so provenance, symbols, and mappings treat each handler independently.
14
+
15
+ - Handlers are evaluated strictly top to bottom and match their exact nominal `ErrorType` identity only. Broad, union, multi-type, and default handlers remain rejected.
16
+ - Semantic validation keeps the Task 011 reachability rule per handler: a handler whose exact nominal identity cannot escape the protected body — including one shadowed by an earlier handler for the same identity — fails with `UNREACHABLE_HANDLER`. Duplicate or shadowed binding names fail with `DUPLICATE_BINDING`.
17
+ - Flow analysis composes as before: the protected body's escaping raises minus each handled identity, unioned with each handler body's raises. Catch entry still discards known-value and optional-presence facts for every outer mutable binding assignable anywhere in the protected body.
18
+ - `finallyBody` runs on every normal and abrupt completion of the try. A completion block that completes abruptly replaces the pending completion **on the paths where it does**, and its own raises escape; on the paths where it completes normally the pending completion passes through unchanged. Both path sets are therefore reachable: the completion block's raises escape *and* the pending raises — from the protected body, from handler bodies, and from earlier statements of the enclosing block — still escape. The try completes normally only when the completion block completes normally and some protected or handler path completes normally. A `try` with no handler is accepted only when it has a completion block.
19
+ - `lifetime.js`, `effects.js`, and `provenance.js` traverse every handler and the completion block; the completion block is a distinct scope that cannot see any handler binding.
20
+
21
+ ## Decision 2 — Rethrow is an explicit statement against one in-scope catch binding
22
+
23
+ `RethrowStatement {binding, bindingNode}` re-propagates the exact caught value of a handler binding that is lexically in scope. `bindingNode` is the exact `CatchBinding` node, so provenance and generated mappings resolve a rethrow to the handler that introduced the binding rather than to a free name.
24
+
25
+ - Frontends accept only a rethrow whose target is a known in-scope catch binding of error type: JavaScript/TypeScript `throw <binding>`, PHP `throw $<binding>`, Java `throw <binding>`, Ruby `raise <binding>` and bare `raise` inside an exact rescue (bare `raise` re-raises the handler's own exception, which is exactly the current binding).
26
+ - A rethrow adds the binding's nominal identity to the enclosing block's escaping raises, so an outer handler for that identity stays reachable and an outer handler for an unraisable identity still fails `UNREACHABLE_HANDLER`.
27
+ - Rethrowing a binding that is not in scope, or throwing any non-binding value, stays rejected (`INVALID_RETHROW` semantically, `UNSUPPORTED_SYNTAX` in a frontend).
28
+ - Ruby `retry`, `redo`, rescue modifiers, and `else` clauses remain rejected. `retry` re-executes the protected body, which is a control-flow construct rather than error propagation, and the semantic model has no node for it; this is a permanent exclusion unless a later task designs one explicitly.
29
+
30
+ ## Decision 3 — JavaScript and TypeScript express ordered multi-catch as the guarded dispatch chain
31
+
32
+ JavaScript and TypeScript have no sibling `catch` clauses for one `try`, so the exact native profile for an ordered handler list is a guarded dispatch chain: each clause keeps the Task 011 `instanceof` guard, and the unmatched branch of a non-final guard rethrows its own binding into a nested `try` whose single clause is the next handler, while the matched branch is the handler body in the guard's `else` block. The final guard keeps the plain unmatched rethrow and its handler body as the following statements.
33
+
34
+ - The backend emits this chain and the frontend flattens it back to one ordered handler list, so generated source reparses to the same meaning and never exposes target-only boilerplate as semantic statements.
35
+ - Faithfulness is the reason for the shape. Nesting the protected body inside successive `try` blocks would let a later handler catch an error raised by an earlier handler body, which the ordered-list meaning forbids: a handler-body raise must escape outward past every remaining sibling handler. In the dispatch chain each handler body runs outside its own guard, so a handler-body raise propagates past all remaining handlers, exactly like a sibling clause in PHP, Ruby, and Java. The `else` branch is what keeps a handled error from also reaching the next clause.
36
+ - The chain is recognized only in its exact emitted form: one binding per clause, the exact guard, the exact rethrow of that clause's own binding, and for a dispatch clause no finalizer or extra statement in the dispatch `try` and no statements after the single guarded `if` in the clause body. A single-handler clause may spell its handler body either as the statements after the plain rethrow or as the guard's `else` block, which is what the backend emits for a one-element list with a completion block. Any other shape is `UNSUPPORTED_SYNTAX`.
37
+
38
+ ## Decision 4 — Checked-error effects in signatures stay permanently excluded
39
+
40
+ No `throws`/`raises` clause, error union, or result type is added to function, constructor, or method signatures, and this is a permanent exclusion for this feature rather than deferred work.
41
+
42
+ - Propagation stays implicit, exactly as Task 011 established: internal fixed-point analysis follows direct calls so a `try` around a helper call is reachable without any declaration, and each call site's escaping error set is derived from resolved callee effects.
43
+ - Cohorts where checked propagation is mandatory are not approximated. Java generation keeps using unchecked `RuntimeException` subclasses and rejects `throws` clauses and checked types; the deferred cohort (Kotlin, Python, C#, Go, C, C++, Rust, Swift, Wasm) continues to reject any typed-error IR with located `UNSUPPORTED_CAPABILITY` — there is no C status code, Rust `Result`, Go error return, or Wasm trap substitute.
44
+ - Constructors and reference-class members may catch errors internally but still cannot expose an unchecked-error effect through a receiver call, consistent with Task 033.
45
+
46
+ ## Decision 5 — Cohort boundaries and diagnostics are unchanged
47
+
48
+ The extension adds no capability to the deferred ten targets and no new public feature flag: `typedErrors` remains true only for the original five. Frontend form errors remain `UNSUPPORTED_SYNTAX`, semantic type/control errors keep their stable semantic codes (`INVALID_ERROR_HANDLER`, `INVALID_RETHROW`, `UNREACHABLE_HANDLER`, `DUPLICATE_BINDING`, `NON_ERROR_RAISE`, `UNKNOWN_ERROR`), and target inability remains `UNSUPPORTED_CAPABILITY` raised before any writer allocation.
49
+
50
+ ## Consequences
51
+
52
+ - Idiomatic multi-handler and `finally`/`ensure` programs in the original five now parse, generate, reparse to one meaning, and execute on real toolchains.
53
+ - Every previously accepted single-handler program keeps its exact meaning; the single-handler shape is the one-element case of the ordered list, and the historical Task 011 and Task 013 fixtures are unchanged.
54
+ - Rejected constructs stay rejected with the same codes, so the extension is additive: broad/default handlers, arbitrary thrown values, union catches, `retry`/`else`/rescue modifiers, try-with-resources, checked signatures, causes/suppression, and non-cohort lowering remain outside the model.
55
+ - JavaScript/TypeScript generated output for multi-handler IR is longer than the source-level ideal, because the guard chain is the price of faithful ordered semantics on a target with one clause per `try`.
@@ -0,0 +1,156 @@
1
+ # ADR: Portable numeric tower — FloatType and DecimalType
2
+
3
+ - Status: `proposed` (accepted on merge of this ADR; implementation proceeds per the follow-up plan below)
4
+ - Issue: STOE-57 (Semantifold core — floating-point and decimal numeric types across all 15 languages)
5
+ - Depends on: the STOE-56 integer-operator contract (division, remainder, exponentiation, bitwise/shift, compound/update assignment) for the integer side of every shared spelling; this ADR does not decide those semantics and never changes them.
6
+
7
+ ## Context
8
+
9
+ Today every program is integer/boolean/string only. Floating point, decimal, and bigint are explicitly unsupported (`docs/language-support.md`, "Diagnostics and limitations"). Idiomatic numeric programs — money, ratios, measurements, unit conversions — cannot be written at all, and the STOE-56 operator work makes the gap sharper: once integer `/` and `%` exist, users will expect `/` on non-integer operands to mean division, not truncation.
10
+
11
+ The repository's guiding principles (AGENTS.md, `todo/README.md`) constrain the design:
12
+
13
+ 1. Model meaning once: the semantic IR carries types and operations, never target spelling.
14
+ 2. Fail loudly: a target that cannot honor a contract reports `UNSUPPORTED_CAPABILITY` before any partial artifact.
15
+ 3. Prefer a precise portable profile: overflow, rounding, and formatting require explicit contracts, not symbol substitution.
16
+ 4. No implicit coercion: equality and arithmetic never mix types silently.
17
+ 5. Existing integer semantics are frozen: safe-range literals, checked arithmetic, and the exit-70 overflow convention stay exactly as delivered.
18
+
19
+ This ADR decides the shape of the numeric tower. Per-language literal spellings, generated-helper shapes, and acceptance corpora are separate per-language specs (see Follow-ups).
20
+
21
+ ## Decision 1 — The tower has three implemented numeric types now; bigint is a separate follow-up
22
+
23
+ `SemanticTypeName` grows from `"integer" | "boolean" | "string"` to include `"float"` and `"decimal"`. `integer` keeps its exact current contract (JavaScript safe-integer literals, per-target checked arithmetic, single unsigned mathematical zero). `float` and `decimal` are new, distinct, non-coercible scalar types.
24
+
25
+ `bigint` is **not** part of this tower. It is deferred to its own task (see Decision 5) because unbounded width changes formatting, equality, collection-key, and target-native stories in ways orthogonal to fixed-width float/decimal.
26
+
27
+ ## Decision 2 — FloatType is IEEE-754 binary64 with a strict fail-loud subset
28
+
29
+ FloatType is exactly the target's native double (`binary64`). The language profile is the **stricter fail-loud subset**, not the full IEEE-754 program: NaN and ±Infinity are **unobservable** — no literal produces them, no accepted operation can produce them, and no builtin (`sqrt`, `log`, …) exists to produce them.
30
+
31
+ ### Literals
32
+
33
+ Canonical decimal float literals only:
34
+
35
+ ```
36
+ float-literal = [ "-" ] digits [ "." digits ] [ ( "e" | "E" ) [ "+" | "-" ] digits ]
37
+ ```
38
+
39
+ - A float literal must carry a fractional part or an exponent: `1.5`, `-2.25`, `1.0`, `1e3`, `1.0e-9` are float; `1` is integer. `1.` and `.5` are rejected (`UNSUPPORTED_SYNTAX`).
40
+ - Underscore separators, hexadecimal/binary floats (`0x1p3`), `f`/`d` suffixes, `inf`, `nan`, and any spelling whose correctly-rounded value is ±Infinity are rejected at the frontend (`NUMERIC_LITERAL_OUT_OF_RANGE` for magnitude, `UNSUPPORTED_SYNTAX` for spelling).
41
+ - A decimal literal maps to its correctly-rounded binary64 value (round-half-even ties). The semantic value is that binary64 value, not the source digits.
42
+
43
+ ### Operations
44
+
45
+ `+`, `-`, `*`, `/`, unary `-`, `<`, `<=`, `>`, `>=`, `==`, `!=` on float operands are the IEEE-754 binary64 operations of the target. `/` is selected by operand type: `integer / integer` is the STOE-56 integer-division contract, `float / float` is IEEE division, `decimal / decimal` is the decimal contract (Decision 3). Mixed numeric operands are rejected (Decision 4).
46
+
47
+ ### Fail-loud policy
48
+
49
+ - **Division by zero** (`x / 0.0`, including a computed zero divisor) is a runtime failure: the program terminates with exit code `70` and the exact single stderr line `semantifold: float arithmetic failure\n`. This matches the delivered Rust/Zig integer-overflow convention (`semantifold: integer overflow\n`, exit 70).
50
+ - **Overflow to non-finite** (a `+`, `-`, `*`, or `/` whose exact result rounds away from the finite binary64 range) is the same runtime failure. Generated code checks finiteness after each float operation using the target's native classification (`isfinite`, `Float.isFinite`, `f64::is_finite`, `f64.isFinite`, `Float::finite?`, `math::isfinite`, …) and raises the identical failure.
51
+ - **Compile-time-known** overflow or division by zero in a literal operation tree is rejected before emission, exactly as integer trees are rejected today.
52
+ - **Underflow** to zero is permitted and observable as `0.0` (or `-0.0` internally). Denormal values are permitted.
53
+ - **NaN is unreachable**: because no accepted operation can produce it (the only IEEE-invalid candidate, `0.0 / 0.0`, is division by zero and fails first), no NaN policy, payload, or quiet/signaling distinction is ever observable. This is the ADR's NaN/Infinity policy: they are excluded from the language, not formatted or compared.
54
+
55
+ ### Equality and ordering
56
+
57
+ Float equality is IEEE value equality on binary64. `-0.0 == 0.0` is true; the sign of zero is otherwise unobservable (printing canonicalizes `-0.0` to `0`, matching the delivered JavaScript/TypeScript `-0` handling). Ordering is IEEE total ordering with the NaN case unreachable. Equality never coerces: `float == integer` and `float == decimal` are validation errors.
58
+
59
+ ### Canonical printed form
60
+
61
+ Printed floats use one canonical text contract, independent of each target's native `toString`/`printf` defaults:
62
+
63
+ 1. **Shortest round-trip**: the shortest decimal digit string that parses back to the exact same binary64 value (Ryu/Grisu-class correctly-rounded shortest representation).
64
+ 2. **Notation rule**: positional notation when the decimal exponent of the value is in `(-5, 17)`; otherwise scientific notation `d.dddde±X` with a lowercase `e`, explicit exponent sign, and no exponent leading zeros.
65
+ 3. **Type-preserving text**: the canonical form always carries a decimal point or an exponent, so a printed float re-parses as a float, never as an integer: `1.0`, not `1`; `2.5e10`, not `25000000000`.
66
+ 4. Negative zero prints as `0` (the single unsigned zero convention).
67
+
68
+ Backends must not print floats through a target's default scalar rendering unless a per-language spec **proves** that native rendering is exactly this contract for the qualified toolchain (JavaScript `Number.prototype.toString`, Python `repr`, Ruby `Float#to_s`, Go `strconv.FormatFloat(v, 'g', -1, 64)`, Rust `{}` for `f64`, Java `Double.toString`, .NET Core-style `double.ToString()`, and Zig `std.fmt` are candidates; C `printf`, C++ iostreams, PHP's `precision`-controlled conversion, and Swift/Dart defaults are not). Where native rendering is not provably conformant, the backend emits a compiler-owned generated shortest-round-trip formatter (the same private-support-module pattern already used for Kotlin checked helpers, Swift Unicode-scalar equality helpers, and the C/C++ ordered-expression runtime). The acceptance corpus pins exact printed bytes per target.
69
+
70
+ ## Decision 3 — DecimalType is fixed-precision 28-significant-digit decimal with round-half-even
71
+
72
+ DecimalType models a fixed-precision decimal number:
73
+
74
+ - **Coefficient**: at most 28 significant digits (chosen because it is the exact intersection of `.NET System.Decimal` and Python `decimal` default precision, and is trivially representable by `java.math.BigDecimal`/`ruby BigDecimal`/`BigInt`/128-bit-integer helpers on every other target).
75
+ - **Scale**: 0 through 28 fractional digits. Scale is part of the value: `1.50` and `1.5` are different decimal values for arithmetic and printing, and both are legal literals.
76
+ - **Literals**: canonical decimal spelling `[ "-" ] digits [ "." digits ]` with at most 28 significant digits and at most 28 fractional digits. No exponent spelling for decimal literals (it would make scale ambiguous). Out-of-range literals report `NUMERIC_LITERAL_OUT_OF_RANGE`. Source spelling per language is a facade question (Decision 6).
77
+ - **Arithmetic**: `+`, `-`, `*`, unary `-`, `/`, `<`, `<=`, `>`, `>=`, `==`, `!=`.
78
+ - `+`/`-`: exact; result scale is `max(scale_a, scale_b)`. A result needing more than 28 significant digits is a runtime failure (exit 70, `semantifold: decimal arithmetic failure\n`).
79
+ - `*`: exact; result scale is `scale_a + scale_b`. If the exact product needs more than 28 significant digits **or** more than 28 fractional digits, the result is rounded to the contract with **round-half-even (banker's rounding)**; if the rounded coefficient still exceeds 28 significant digits, it is the same runtime failure.
80
+ - `/`: division is total for nonzero divisors. The exact quotient is used when representable within 28 significant digits and scale ≤ 28; otherwise the quotient is rounded to scale 28 with **round-half-even**. Division by zero is the exit-70 runtime failure.
81
+ - Compile-time-known decimal overflow or division by zero is rejected before emission.
82
+ - **Rounding rule is round-half-even everywhere** this type rounds (literal rounding is exact by grammar; only `*` scale reduction and `/` round). This is the ADR's explicit "banker's rounding" decision; no target's native half-up default may leak through — backends must pin the rounding mode explicitly (`MathContext(28, RoundingMode.HALF_EVEN)`, `decimal.localcontext(prec=28, rounding=ROUND_HALF_EVEN)`, `MidpointRounding.ToEven`, generated helpers' explicit tie rule).
83
+ - **Equality** is numeric-value equality and is **scale-insensitive**: `1.50 == 1.5` is true. Ordering compares numeric values. Equality never coerces across `integer`/`float`/`decimal`.
84
+ - **Printed form** is exact positional notation preserving the value's scale, including trailing zeros: `1.50` prints as `1.50`. No exponent form. This makes decimal printing deterministic on every target by construction.
85
+
86
+ ## Decision 4 — No implicit promotion, ever
87
+
88
+ There is no implicit `integer → float`, `integer → decimal`, or `float → decimal` conversion in any operand position: not in arithmetic, comparisons, equality, assignments, initializers, arguments, returns, or print. A mixed numeric operand pair is a located `TYPE_MISMATCH` at the operator, exactly like today's mixed-type rules. `float` and `decimal` are first-class declared types with their own per-language source spellings (per-language specs).
89
+
90
+ Explicit conversion builtins (`toFloat`, `toDecimal`, `truncate`, `round`) are **out of scope for this issue** and are a follow-up candidate: they are value-changing operations whose rounding contracts must be specified separately, and the fail-loud design does not need them to make the tower usable (literals already carry their type).
91
+
92
+ ## Decision 5 — bigint is a separate follow-up task
93
+
94
+ bigint is deliberately excluded from this tower:
95
+
96
+ - Its width is unbounded, so "target-native representation" means `java.math.BigInteger`, Python `int`, Ruby `Integer`, .NET `System.Numerics.BigInteger`, `BigInt` in JS, and generated multi-limb helpers in C/C++/Go/Rust/Swift/Dart/Zig/PHP/Wasm — a materially different support matrix than "native double" for float.
97
+ - Its equality/formatting interaction with the existing safe-integer contract is a trap: Python and Ruby `int` are already unbounded, Go `int64` is architecture-sized, and Java `int` is 32-bit. Adding bigint without re-deciding the integer contract would silently change what `integer` means on those targets.
98
+ - The practical value (exact large counting, IDs) is separable from the practical value of float/decimal (ratios, money).
99
+
100
+ Follow-up: a dedicated bigint task that first decides whether `integer` itself becomes unbounded on dynamic targets or bigint remains a separate type, then proceeds with the same ADR → per-language specs → acceptance chain.
101
+
102
+ ## Decision 6 — Target-native representations
103
+
104
+ Float is native on every target; decimal is native where conformant and compiler-owned-support elsewhere, consistent with the hub-and-spoke stdlib design (`docs/standard-library-portability.md`): source-language compatibility facades carry the literal/operation spellings, canonical contracts join them, and target host providers own the native bindings. Application source never resolves provider names.
105
+
106
+ | Target | `float` representation | `decimal` representation |
107
+ | --- | --- | --- |
108
+ | PHP | native `float` | generated compiler-owned fixed-precision helper (no conformant native decimal; GMP is not guaranteed) |
109
+ | Ruby | native `Float` | native `BigDecimal` (compiler-owned `require "bigdecimal"` scaffold, pinned precision context) |
110
+ | JavaScript | native `number` | generated compiler-owned helper over `BigInt` (unscaled + scale) |
111
+ | TypeScript | native `number` | same generated helper, typed |
112
+ | Java | native `double` | native `java.math.BigDecimal` with pinned `MathContext(28, HALF_EVEN)` |
113
+ | Kotlin | native `Double` | native `java.math.BigDecimal` with pinned `MathContext` |
114
+ | Python | native `float` | native `decimal.Decimal` under a compiler-owned `decimal.localcontext(prec=28, rounding=ROUND_HALF_EVEN)` scaffold |
115
+ | C# | native `double` | native `System.Decimal` (28–29 digits; the 28-digit contract sits inside it; division truncation at scale 28 is pinned explicitly) |
116
+ | Go | native `float64` | generated compiler-owned helper (no standard decimal type; `math/big` imports stay outside the generated-support boundary) |
117
+ | C | native `double` | generated compiler-owned helper over `__int128` unscaled + scale (qualified Clang 21) |
118
+ | C++ | native `double` | generated compiler-owned helper over `__int128` unscaled + scale |
119
+ | Rust | native `f64` | generated compiler-owned helper over `i128` unscaled + scale (checked) |
120
+ | Swift | native `Double` | generated compiler-owned helper over `Int128` (Swift 6.3 stdlib) unscaled + scale |
121
+ | Dart | native `double` | generated compiler-owned helper over `BigInt` unscaled + scale |
122
+ | Zig | native `f64` | generated compiler-owned helper over `i128` unscaled + scale (checked) |
123
+ | Wasm | native `f64` ops (`f64.add/sub/mul/div`, `f64.lt/le/gt/ge/eq`) | generated multi-limb helper over `i64` pairs in linear memory |
124
+
125
+ Every generated helper is compiler-owned private support (same class as the existing Kotlin checked-integer helpers, Swift Unicode-scalar helpers, C/C++ ordered-expression runtime, and Wasm scratch arena), emitted only when reachable, and never name-resolvable from portable source.
126
+
127
+ ## Interaction with existing integer checked arithmetic
128
+
129
+ - Nothing in this ADR changes integer literals, integer checked arithmetic, the safe-integer range, Java's 32-bit profile, Go's wrap behavior exception, or the exit-70 overflow convention. `integer` is untouched.
130
+ - The `/` and `%` spellings introduced by STOE-56 are **type-selected operations**, not overloaded symbols: the semantic operation is chosen by the resolved operand types before any target spelling exists (`IntegerDivide` per STOE-56's contract; `FloatDivide`; `DecimalDivide`). A mixed pair never selects an operation.
131
+ - Integer operands may not appear where a float or decimal operation is selected, and vice versa; there is no promotion path and no common "numeric" supertype.
132
+ - The fail-loud runtime convention is shared: numeric failures exit 70 with one fixed stderr line per family (`semantifold: integer overflow`, `semantifold: float arithmetic failure`, `semantifold: decimal arithmetic failure`).
133
+
134
+ ## Rejection cases and diagnostics
135
+
136
+ - Mixed numeric operands (arithmetic, comparison, equality, assignment, argument, return): located `TYPE_MISMATCH`.
137
+ - Unsupported or malformed float/decimal literal spelling: located `UNSUPPORTED_SYNTAX` (including `1.`, `.5`, underscores, hex floats, suffixes, `inf`/`nan`).
138
+ - Literal magnitude/scale outside the type contract: `NUMERIC_LITERAL_OUT_OF_RANGE`.
139
+ - Compile-time-known float non-finite result or decimal overflow/division-by-zero in a literal tree: located `NUMERIC_LITERAL_OUT_OF_RANGE` before emission (same posture as integer tree rejection).
140
+ - A target whose qualified toolchain cannot honor the contract for a requested module: located `UNSUPPORTED_CAPABILITY` before any partial artifact (fail-loud, never a silently different behavior).
141
+ - Runtime float division-by-zero/overflow and decimal overflow/division-by-zero: exit 70 with the family's fixed stderr line.
142
+
143
+ ## Consequences
144
+
145
+ - The semantic IR gains `FloatLiteral`, `DecimalLiteral`, `FloatType`/`DecimalType` scalar names, and the corresponding typed operations; all fifteen frontends and all text backends plus Wasm must implement the exact profiles or reject transactionally.
146
+ - Printed float output becomes a specified canonical contract, which means several backends gain generated support code rather than using native printing — accepted deliberately: determinism outranks emission brevity, and the private-support-module pattern is already established.
147
+ - Decimal's scale-sensitive value model makes `1.50 != 1.5` as values but equal as numbers; documentation must state this plainly because it surprises most readers.
148
+ - The 28-digit decimal contract is a real limitation (no 34-digit `decimal64`, no arbitrary precision); it is chosen for exact native conformance on the widest set of qualified toolchains rather than maximum precision.
149
+ - Existing programs are unaffected: no existing literal, operator, or print behavior changes, and every existing spec stays green.
150
+
151
+ ## Follow-ups (implementation chain)
152
+
153
+ 1. Per-language specs (one focused spec file per language, per repo convention): exact frontend literal/type spellings, facade shapes for decimal where native spelling is absent, backend emission and helper shapes, native-rendering conformance proofs for float printing, and the accept/reject corpus including division-by-zero, overflow, rounding-tie, and scale cases.
154
+ 2. Shared semantic validation and IR extension specs; Wasm lowering spec.
155
+ 3. Real-toolchain acceptance per target in the canonical lane (and exact-head TensorBuzz CI), with byte-exact printed output pinned.
156
+ 4. Separate follow-up issues: explicit conversion builtins (with their own rounding contracts) and the bigint decision task.
@@ -5,7 +5,7 @@ Semantifold has three explicit layers.
5
5
  Task 018's C path follows these layers through the unchanged root-bundled private legacy CST boundary. `src/frontends/c.js` owns all C trees and exhaustive profile validation. `src/backends/native-validation.js` checks native graph/location limits and `c-validation.js` checks C memory limits; `ordered-expressions.js` plans occurrence-specific evaluation without modifying IR; `c.js` emits mapped `program.c`; and `c-runtime.js` supplies the synthetic immutable-slice/arena header. The C frontend reconstructs candidates only from complete CST regions, resolves them with the existing semantic validator, and compares the original whole CST against the re-parsed canonical backend plan before returning a module. This same-language validation keeps target generation free of parser objects. See the [C profile](c.md).
6
6
 
7
7
  1. Frontend adapters in `src/frontends/` invoke mature language parsers and translate only recognized syntax. Babel owns JavaScript and TypeScript parsing, `php-parser` owns PHP, Prism owns Ruby, Lezer owns Java, and qualified Tree-sitter bindings and grammars own Python, C#, Go, C, CPP, Rust, Swift, Kotlin, Dart, and Zig. Their AST/CST objects never appear in the public API. Adapters enumerate supported children and reject every other parser node; filtering syntax away is not a normalization strategy. Prism node offsets and comment byte ranges are converted from UTF-8 to JavaScript UTF-16 offsets before semantic location construction and metadata source slicing. Tree-sitter adapters reject lone surrogates, exhaustively traverse named/anonymous/comment/directive/error/missing children, and verify reported boundaries through the shared UTF-8-byte-to-UTF-16 converter. The Go adapter additionally collapses only exact `package main`, optional `fmt`, ordered semantic functions, and a final zero-argument `func main()` into the shared module/entry shape; assignment occurrences, not Go's uniformly mutable `var` syntax, determine semantic local mutability.
8
- 2. `src/semantic/` defines the shared JSDoc-typed discriminated representation and one-based, filename-bearing UTF-16 source locations. `provenance.js` indexes exact source content, deterministic semantic paths/node IDs, resolved symbol IDs, parser-token subranges, and closed source/derived/synthetic origins without exposing parser objects. Task 010's `SemanticProgram` adds dependency-first logical modules, a caller-order multi-source registry, explicit import/export nodes, one selected entry module, and stable module-qualified declaration identities; native filenames, package names, and target paths remain outside semantic resolution. Value types are scalar `integer`/`boolean`/`string` references or recursive acyclic `ListType`, unordered string-keyed `MapType`, insertion-ordered `OrderedMapType`, `OptionalType`, nominal `RecordType`, nominal Task 033 `ReferenceType`, and Task 034 linear `OwnedResourceType`/`OwnedReferenceType`; `void` exists only as a callable return. Task 012 adds invariant unbounded declaration-owned `TypeParameter` values, scoped `TypeVariableReference` leaves, and exact ordered arguments on generic record types. Task 011 adds nominal `ErrorType` only for catch bindings and error constructions, never as an ordinary parameter/local/return value. Task 034 adds compiler-authorized capability/resource/failure/operation declarations, complete resolved `EffectCallExpression` sites, and explicit `OwnedMoveExpression`/`OwnedBorrowExpression` nodes. Every function and reference class/member has a deterministic declaration identity; every list, ordered-map, condition-controlled loop, capability member, and effect site has a deterministic module-local identity. Every direct function, constructor, receiver method, and effect call carries exact resolution facts. Literal and access nodes distinguish scalars, collections, presence/absence, nominal values/references/resources, errors, receivers, and private fields. Unary and binary expressions carry a closed semantic `operation` and explicit result `type`, never a source operator symbol. Every function, constructor, method, entry point, branch, loop, try body, and catch body owns a located `Block` containing ordered `Statement` nodes. Statements additionally include exact `ForEachMapStatement`, strict-Boolean precondition `WhileStatement`, identity-resolved `BreakStatement`/`ContinueStatement`, private receiver-field writes, `RaiseStatement`, and one-handler `TryStatement`; general exception constructs have no semantic node. `lifetime.js` retains distinct normal/return/raise/break/continue ownership states and proves every owner terminal or transferred at every reachable scope exit. Task 033 class identity/private state remains single-module semantics; Task 034's effect/resource nodes stay compiler-authorized and single-module by default, Task 035 extends Task 010 `SemanticProgram` with versioned canonical capability modules and negotiated provider link requirements, and Task 036 adds exact facade-resolution evidence plus compiler-owned source/module roles and a complete selected-facade descriptor.
8
+ 2. `src/semantic/` defines the shared JSDoc-typed discriminated representation and one-based, filename-bearing UTF-16 source locations. `provenance.js` indexes exact source content, deterministic semantic paths/node IDs, resolved symbol IDs, parser-token subranges, and closed source/derived/synthetic origins without exposing parser objects. Task 010's `SemanticProgram` adds dependency-first logical modules, a caller-order multi-source registry, explicit import/export nodes, one selected entry module, and stable module-qualified declaration identities; native filenames, package names, and target paths remain outside semantic resolution. Value types are scalar `integer`/`boolean`/`string` references or recursive acyclic `ListType`, unordered string-keyed `MapType`, insertion-ordered `OrderedMapType`, `OptionalType`, nominal `RecordType`, nominal Task 033 `ReferenceType`, and Task 034 linear `OwnedResourceType`/`OwnedReferenceType`; `void` exists only as a callable return. Task 012 adds invariant unbounded declaration-owned `TypeParameter` values, scoped `TypeVariableReference` leaves, and exact ordered arguments on generic record types. Task 011 adds nominal `ErrorType` only for catch bindings and error constructions, never as an ordinary parameter/local/return value. Task 034 adds compiler-authorized capability/resource/failure/operation declarations, complete resolved `EffectCallExpression` sites, and explicit `OwnedMoveExpression`/`OwnedBorrowExpression` nodes. Every function and reference class/member has a deterministic declaration identity; every list, ordered-map, condition-controlled loop, capability member, and effect site has a deterministic module-local identity. Every direct function, constructor, receiver method, and effect call carries exact resolution facts. Literal and access nodes distinguish scalars, collections, presence/absence, nominal values/references/resources, errors, receivers, and private fields. Unary and binary expressions carry a closed semantic `operation` and explicit result `type`, never a source operator symbol. Every function, constructor, method, entry point, branch, loop, try body, and catch body owns a located `Block` containing ordered `Statement` nodes. Statements additionally include exact `ForEachMapStatement`, strict-Boolean precondition `WhileStatement`, identity-resolved `BreakStatement`/`ContinueStatement`, private receiver-field writes, `RaiseStatement`, `RethrowStatement` with its exact `CatchBinding` node, and `TryStatement` with an ordered `CatchHandler[]` list plus an optional `finallyBody` completion block; general exception constructs beyond that model have no semantic node. `lifetime.js` retains distinct normal/return/raise/break/continue ownership states and proves every owner terminal or transferred at every reachable scope exit. Task 033 class identity/private state remains single-module semantics; Task 034's effect/resource nodes stay compiler-authorized and single-module by default, Task 035 extends Task 010 `SemanticProgram` with versioned canonical capability modules and negotiated provider link requirements, and Task 036 adds exact facade-resolution evidence plus compiler-owned source/module roles and a complete selected-facade descriptor.
9
9
  3. Source and binary backends in `src/backends/` validate their semantic capabilities before emitting target syntax or bytes. Shared validation owns recursive block shape, reachability and all-path abrupt-completion analysis, recursively complete expressions, exhaustive operation/type signatures, function signatures, lexical binding scopes, mutability, declaration-before-use, strict condition/return/argument/initializer/assignment types, fixed-point propagation of nominal unchecked errors, and Task 034 capability/effect/resource graph integrity. `backends/effects.js` rejects unsupported authority/target/context combinations before writer allocation and plans nested eager effects into statement-local collision-safe temporaries in semantic evaluation order. Dependency-first Task 010 validation carries ordinary inferred error sets across resolved function-import identities; both single-module generation and program mode accept compiler-authorized capability modules, while program mode additionally resolves their canonical standard-library contract references and links negotiated target host provider artifacts before any writer allocation. Target-specific validation adds identifier rules, entry-scaffolding name collisions, numeric/memory/resource bounds, and target string representation. The single-module original five retain one fixed compiler-owned Task 034 probe binding, and program mode materializes the same probe behavior as a tree-shaken provider artifact under a protected native binding. Text emitters write through `SourceWriter`; the Wasm encoder writes through an annotated binary writer so every byte range is semantic or explicitly synthetic. A backend either returns a complete executable program/project/application set or raises `UNSUPPORTED_CAPABILITY`; it never silently drops nodes, exposes a partial artifact, or lets an emitter discover malformed shape through a native error.
10
10
 
11
11
  `src/language-registry.js` is the sole ordered dispatch and discovery source. Its deeply frozen records bind implementations to independent frontend, text-backend, binary-backend, application-backend, interoperability, and non-executing developer-check capabilities. Public descriptors derive role booleans, semantic feature flags, artifact multiplicity, round-trip support, rich/v3/byte mapping forms, acceptance stages/toolchains, and the immutable check `supported`/stage/tool subset from those records; all fifteen text targets advertise target-owned check plans. `generalFunctionsAndCalls` is true for PHP, Ruby, JavaScript, TypeScript, Java, Dart, and Zig; every later feature flag remains true only for the original five. `supportedLanguages` is the derived compatibility view containing IDs with both a frontend and text backend. Duplicate IDs, unknown record fields, malformed capabilities, unordered acceptance stages, executing developer-check stages, and undeclared check tools fail as `INVALID_REGISTRY`.
@@ -206,18 +206,18 @@ Each parser adapter recognizes only its canonical native module form: named rela
206
206
 
207
207
  ## Task 011 typed errors and handling
208
208
 
209
- The original-five cohort implements one nominal unchecked-error model: `ErrorDeclaration`, identity-bound `ErrorType`, `ErrorConstruction` with exactly one semantic string message, abrupt `RaiseStatement`, and `TryStatement` with one exact `catchType`, one immutable block-local `CatchBinding`, and one `catchBody`. `ErrorMessageRead` is the only portable member operation. A handler removes only its exact nominal identity from the protected block's escaping errors; unmatched errors and errors raised by the handler continue outward. Catch entry discards known-value and optional-presence facts for every outer mutable binding assignable anywhere in the protected body, because a raise may transfer control after any preceding assignment. Internal fixed-point analysis follows direct calls so a catch around a helper call is reachable without adding checked exception declarations or public effect lists. Raise, return, break, and continue remain abrupt for reachability and return-completeness analysis.
209
+ The original-five cohort implements one nominal unchecked-error model: `ErrorDeclaration`, identity-bound `ErrorType`, `ErrorConstruction` with exactly one semantic string message, abrupt `RaiseStatement`, `RethrowStatement` re-propagating one in-scope catch binding through its exact `CatchBinding` node, and `TryStatement` with an ordered `CatchHandler[]` list plus an optional `finallyBody` completion block. Each handler carries one exact `catchType`, one immutable block-local `CatchBinding`, and one `catchBody`. `ErrorMessageRead` is the only portable member operation. Handlers are evaluated top to bottom, and a handler removes only its exact nominal identity from the protected block's escaping errors; unmatched errors and errors raised by a handler continue outward past every remaining sibling handler, and a handler whose exact identity cannot escape — including one shadowed by an earlier handler for the same identity — fails `UNREACHABLE_HANDLER`. Catch entry discards known-value and optional-presence facts for every outer mutable binding assignable anywhere in the protected body, because a raise may transfer control after any preceding assignment. `finallyBody` runs on every normal and abrupt completion; a completion block that itself completes abruptly replaces the pending completion and its raises, while a normally completing completion block passes the pending completion through unchanged. A `try` without handlers is accepted only with a completion block. Internal fixed-point analysis follows direct calls so a catch around a helper call is reachable without adding checked exception declarations or public effect lists. Raise, return, break, and continue remain abrupt for reachability and return-completeness analysis.
210
210
 
211
211
  The accepted parser-backed forms are deliberately exact:
212
212
 
213
- - Ruby uses an empty `class E < StandardError`, `raise E, message`, and `begin` with one explicit `rescue E => error`. Default/broad or multiple rescue, rescue modifiers, bare raise/re-raise, retry, else, ensure, and fatal/system classes are rejected.
214
- - JavaScript/JSDoc and TypeScript use an empty `class E extends Error`, `throw new E(message)`, and one catch whose first statement is the exact `if (!(error instanceof E)) { throw error }` unmatched-value guard. An imported error alias resolves as a constructor only when no parameter, local, iteration, or catch binding shadows that spelling. Unguarded catches, alternate narrowing/assertions, arbitrary thrown values, `AggregateError`, causes, and `finally` are rejected. The guard is target syntax only and reparses back into the single semantic handler.
215
- - PHP uses `final class E extends RuntimeException`, exact construction/throw, and one `catch (E $error)`. Multi-catch, `Throwable`/`Exception`/`Error`, previous exceptions, throw expressions in value contexts, and `finally` are rejected. Generated namespaced projects qualify the native `\RuntimeException` base explicitly.
216
- - Java uses a final unchecked `RuntimeException` subclass with exactly one `String message` constructor forwarding to `super(message)`, exact construction/throw, and one typed catch. Checked exceptions, `throws`, multi-catch, resources, broad catches, suppressed/cause behavior, and `finally` are rejected.
213
+ - Ruby uses an empty `class E < StandardError`, `raise E, message`, `raise <binding>` or bare `raise` inside one exact rescue, and `begin` with an ordered list of explicit `rescue E => error` clauses plus one optional `ensure` block. Default/broad rescue, rescue modifiers, retry, redo, else, and fatal/system classes are rejected.
214
+ - JavaScript/JSDoc and TypeScript use an empty `class E extends Error`, `throw new E(message)`, `throw <binding>`, and catches whose first statement is the exact `if (!(error instanceof E)) { throw error }` unmatched-value guard. Because the language has no sibling catch clauses, an ordered multi-handler list is written and emitted as a guarded dispatch chain: each non-final guard rethrows its own binding into a nested `try` whose single clause is the next handler, and each handler body runs outside its guard so a handler-body raise escapes past every remaining handler. An imported error alias resolves as a constructor only when no parameter, local, iteration, or catch binding shadows that spelling. Unguarded catches, alternate narrowing/assertions, arbitrary thrown values, `AggregateError`, causes, and any deviation from the exact chain shape are rejected. The guards and dispatch nesting are target syntax only and reparse back into one ordered semantic handler list, together with an optional native `finally` block.
215
+ - PHP uses `final class E extends RuntimeException`, exact construction/throw, `throw $<binding>`, an ordered list of `catch (E $error)` clauses, and one optional `finally` block. Broad `Throwable`/`Exception`/`Error` catches, previous exceptions, throw expressions in value contexts, and rethrow of a non-binding value are rejected. Generated namespaced projects qualify the native `\RuntimeException` base explicitly.
216
+ - Java uses a final unchecked `RuntimeException` subclass with exactly one `String message` constructor forwarding to `super(message)`, exact construction/throw, `throw <binding>`, an ordered list of typed catches, and one optional `finally` block. Checked exceptions, `throws`, union catches, resources, broad catches, and suppressed/cause behavior are rejected.
217
217
 
218
218
  Error declaration/type/construction, raise, try/body, catch type/binding/body, and message-read nodes participate in deterministic source provenance, symbols, rich mappings, and Source Map v3. Task 010 programs preserve module-qualified `module#error:N` identities through imports, generation, and reparse. All backend graph, location, target-name, handler-shape, binding, payload, and effect validation completes before writer allocation. Kotlin, Python, C#, Go, C, C++, Rust, Swift, and browser Wasm advertise `typedErrors: false` and reject any Task 011 IR with located `UNSUPPORTED_CAPABILITY`; there is no C status-code, Rust `Result`, Wasm trap, or other substitute.
219
219
 
220
- This feature does not model arbitrary error values, multiple/union/broad handlers, checked effects, error result types, stack traces, causes, suppression, fatal/process errors, asynchronous rejection, `finally`/`ensure`, general classes/methods, or standard-library portability. It adds no pairwise rewrite and does not expand Task 013's historical Tasks 001–008 cohort.
220
+ This feature does not model arbitrary error values, union/broad/default handlers, checked effects or `throws` signatures (a permanent exclusion — see the [typed-error handling extension ADR](adr/20261026-typed-error-handling-extension.md)), error result types, stack traces, causes, suppression, fatal/process errors, asynchronous rejection, Ruby `retry`/`redo`/`else`/rescue modifiers, try-with-resources, general classes/methods, or standard-library portability. It adds no pairwise rewrite and does not expand Task 013's historical Tasks 001–008 cohort.
221
221
 
222
222
  ## Task 012 type parameters and generic declarations
223
223
 
@@ -354,7 +354,7 @@ All fifteen frontends preserve exact input content and parser-backed semantic to
354
354
 
355
355
  `UNSUPPORTED_LANGUAGE` is exclusive to unknown or misspelled IDs. `UNSUPPORTED_ROLE` reports a known ID without the requested frontend, text/binary/application backend, artifact, or interoperability role, including C#, Go, C, Rust, Dart, and Zig through a legacy single-artifact API. `UNSUPPORTED_CAPABILITY` reports a role that exists but cannot represent the requested semantic module or target feature. `UNSUPPORTED_SYNTAX` reports parsed constructs outside the subset with the best parser-provided source location. Missing public annotations use `MISSING_TYPE`; a present unsupported type is `UNSUPPORTED_SYNTAX` at that annotation. Parser failures use `PARSE_ERROR`. Semantic resolution/type failures use `DUPLICATE_BINDING`, `UNRESOLVED_BINDING`, `USE_BEFORE_DECLARATION`, `IMMUTABLE_ASSIGNMENT`, and `TYPE_MISMATCH`; Task 005 additionally uses `VOID_AS_VALUE`, `VOID_RETURN_VALUE`, and `MISSING_RETURN_VALUE`. Task 033 adds `DUPLICATE_CLASS`, `DUPLICATE_PRIVATE_FIELD`, `DUPLICATE_CONSTRUCTOR`, `DUPLICATE_METHOD`, `INCOMPLETE_INITIALIZATION`, `ILLEGAL_PRIVATE_ACCESS`, `UNKNOWN_CLASS`, `INVALID_METHOD_RECEIVER`, `UNKNOWN_METHOD`, `CONSTRUCTOR_ARITY_MISMATCH`, and `METHOD_ARITY_MISMATCH`. Task 034 adds `MISSING_CAPABILITY_AUTHORITY`, `INVALID_CAPABILITY_AUTHORITY`, `UNDECLARED_EFFECT`, `UNDECLARED_FAILURE`, `INVALID_EFFECT_ORDER`, `RESOURCE_TYPE_MISMATCH`, `RESOURCE_MOVE_REQUIRED`, `INVALID_RESOURCE_TRANSFER`, `RESOURCE_ALIAS`, `INVALID_RESOURCE_BORROW`, `BORROW_ESCAPE`, `USE_AFTER_MOVE`, `USE_AFTER_CLOSE`, `RESOURCE_STATE_MISMATCH`, `LEAKED_RESOURCE`, and `FAILURE_ABSENCE_CONFLATION`; a repeated close in the bounded probe uses its declared terminal failure rather than `DOUBLE_CLOSE`. Task 035 adds `STDLIB_CONTRACT_INVALID`, `STDLIB_MODULE_UNKNOWN`, `STDLIB_OPERATION_UNDECLARED`, `STDLIB_VERSION_INCOMPATIBLE`, `STDLIB_VERSION_MALFORMED`, `STDLIB_PROVIDER_INVALID`, `STDLIB_PROVIDER_MISSING`, `STDLIB_PROVIDER_AMBIGUOUS`, `STDLIB_PROVIDER_DEPENDENCY_MISSING`, `STDLIB_PROVIDER_CYCLE`, `STDLIB_LINK_FAILURE`, and `STDLIB_NAME_COLLISION`. Task 036 adds `STDLIB_FACADE_INVALID`, `STDLIB_FACADE_IDENTITY_UNPROVED`, `STDLIB_FACADE_MEMBER_UNSUPPORTED`, `STDLIB_FACADE_VERSION_MALFORMED`, `STDLIB_FACADE_VERSION_INCOMPATIBLE`, `STDLIB_FACADE_DYNAMIC_REFERENCE`, `STDLIB_FACADE_REFLECTION_UNSUPPORTED`, `STDLIB_FACADE_NATIVE_EXTENSION_UNSUPPORTED`, `STDLIB_FACADE_SHADOWED`, `STDLIB_FACADE_REOPENED`, `STDLIB_FACADE_AUTHORITY_FORGED`, `STDLIB_FACADE_NAME_COLLISION`, `STDLIB_FACADE_DECLARATION_MISMATCH`, and `STDLIB_FACADE_DESCRIPTOR_INVALID`; Task 037 additionally uses `STDLIB_PROTECTED_BINDING_ACCESS` for portable attempts to reach canonical operations directly. Resolution failures retain parser locations, while descriptor/link failures are transactional. Flow failures use `NON_BOOLEAN_CONDITION`, `MISSING_RETURN`, `UNREACHABLE_STATEMENT`, `ILLEGAL_RETURN_CONTEXT`, `ILLEGAL_BREAK_CONTEXT`, `ILLEGAL_CONTINUE_CONTEXT`, `INVALID_LOOP_TARGET`, `CROSS_FUNCTION_LOOP_TARGET`, and `UNSUPPORTED_STATEMENT`. Backends validate complete recursive block shape, flow, scalar payloads, binding/type agreement, call resolutions, mutability, loop context, identifiers, target bounds, and fixed artifact contracts before emission. Missing or structurally invalid caller IR reports located `UNSUPPORTED_CAPABILITY` rather than leaking a native error or producing partial source. A non-`Main.java` Java basename, non-`Program.cs` C# filename, non-`main.go` Go filename, non-`program.c` C filename, non-`src/main.rs` Rust filename, non-`bin/program.dart` Dart filename, or non-`src/main.zig` Zig filename uses that same contract. Artifact-set generation requires a non-empty string language/target ID and accepts only the explicit `text`, `binary`, and `application` backend-role strings; malformed IDs or roles report `INVALID_ARTIFACT_SET` without coercing caller input, while an unknown string ID reports `UNSUPPORTED_LANGUAGE` and a valid role absent from a known registry record reports `UNSUPPORTED_ROLE`. Optional registry roles and backend metadata may be omitted with `undefined`; present malformed values, including `null`, report `INVALID_REGISTRY`. Invalid registry, artifact-set, byte-map, discovery, and runner inputs have their own normalized diagnostics rather than leaking native type or subprocess errors.
356
356
 
357
- Boxed or coercive types, inferred public types, TypeScript literal/enum/`any`/`unknown`/arbitrary-union types, PHP aliases or implicit nullable declarations, Ruby `boolish`, Java `Boolean`/`char`, universal null values, floating point, decimal, bigint, symbols, regex values, and byte strings are unsupported. Task 048 admits exactly two expression families: strict-Boolean conditional (ternary) expressions in the original-five cohort, and parser-proved string interpolation in PHP, Ruby, JavaScript, and TypeScript lowered to checked `StringConcat` concatenation of pre-rendered parts with `string`-typed segments only. Division, remainder, exponentiation, bitwise/shift/update/compound operations, Ruby/PHP `and`/`or`, loose equality, nullish forms, optional/safe navigation, ranges, general casts/assertions, collection/object equality, operator overloading, implicit coercion, and constant folding are also excluded; the sole assertion exception is Task 006's exact TypeScript `Map#get(...)!` wrapper, whose lookup must be independently total. Task 007 supplies only the exact explicit optional model, Task 008 only exact ordered list loops, Task 010 only explicit supplied source projects, Task 011 only the exact nominal unchecked-error model above, Task 012 only the exact unbounded invariant generic profile above, Task 014 only the exact ordered-map pair loop above, Task 032 only the exact strict-Boolean precondition loop above, Task 033 only the exact closed reference-class profile above, Task 034 only the explicit single-module probe effect/resource profile above, and Task 048 only the exact cohort ternary and interpolation lowering above. There is no truthiness, implicit-return or general statement-value model, async/generator model, open/inherited/virtual class model, implicit import discovery, post-condition/basic/range loops, arbitrary or ordinary-map iteration, switches, exception behavior beyond Task 011 and declared Task 034 failures, overloads, advanced generic/effect type systems, implicit resource cleanup, unsafe native handles, package-manager resolution, or source-format preservation. The sole value-free expression statements are resolved void function and method calls in the original-five cohort. Ruby type comments remain a bounded RBS-style convention rather than a full signature parser, and single-module Java generation retains the fixed `Main.java` contract.
357
+ Boxed or coercive types, inferred public types, TypeScript literal/enum/`any`/`unknown`/arbitrary-union types, PHP aliases or implicit nullable declarations, Ruby `boolish`, Java `Boolean`/`char`, universal null values, floating point, decimal, bigint, symbols, regex values, and byte strings are unsupported. The portable floating-point/decimal numeric tower is decided in the [numeric tower ADR](adr-numeric-tower.md) and remains unimplemented; nothing in that ADR changes the integer semantics documented above, and `bigint` stays a separate follow-up decision. Task 048 admits exactly two expression families: strict-Boolean conditional (ternary) expressions in the original-five cohort, and parser-proved string interpolation in PHP, Ruby, JavaScript, and TypeScript lowered to checked `StringConcat` concatenation of pre-rendered parts with `string`-typed segments only. Division, remainder, exponentiation, bitwise/shift/update/compound operations, Ruby/PHP `and`/`or`, loose equality, nullish forms, optional/safe navigation, ranges, general casts/assertions, collection/object equality, operator overloading, implicit coercion, and constant folding are also excluded; the sole assertion exception is Task 006's exact TypeScript `Map#get(...)!` wrapper, whose lookup must be independently total. Task 007 supplies only the exact explicit optional model, Task 008 only exact ordered list loops, Task 010 only explicit supplied source projects, Task 011 only the exact nominal unchecked-error model above, Task 012 only the exact unbounded invariant generic profile above, Task 014 only the exact ordered-map pair loop above, Task 032 only the exact strict-Boolean precondition loop above, Task 033 only the exact closed reference-class profile above, Task 034 only the explicit single-module probe effect/resource profile above, and Task 048 only the exact cohort ternary and interpolation lowering above. There is no truthiness, implicit-return or general statement-value model, async/generator model, open/inherited/virtual class model, implicit import discovery, post-condition/basic/range loops, arbitrary or ordinary-map iteration, switches, exception behavior beyond Task 011 and declared Task 034 failures, overloads, advanced generic/effect type systems, implicit resource cleanup, unsafe native handles, package-manager resolution, or source-format preservation. The sole value-free expression statements are resolved void function and method calls in the original-five cohort. Ruby type comments remain a bounded RBS-style convention rather than a full signature parser, and single-module Java generation retains the fixed `Main.java` contract.
358
358
 
359
359
  CPP uses its own [C++20 scalar and scaffold profile](cpp.md), official CPP grammar, one `program.cpp`, by-value owned strings and checked arithmetic. The shared native occurrence planner is backend-only; CPP owns its exact marker and CST recognizer. Native O0/O2 and ASan/UBSan/leak execution are mandatory.
360
360
 
package/docs/testing.md CHANGED
@@ -53,7 +53,7 @@ Task 013 is the separate terminal original-five aggregate gate. Task 005 does no
53
53
 
54
54
  ## Task 011 typed errors and handling
55
55
 
56
- Task 011 uses six focused specs. Semantic validation covers exact nominal types, string-only construction, abrupt raise, exact handler consumption, unmatched and handler-originated propagation, immutable scoped catch bindings, catch-entry invalidation of optional-presence facts after protected-body assignments, message-only access, unreachable handlers, and fixed-point direct-call effects without checked signatures. Frontend coverage parses the canonical Ruby, JavaScript/JSDoc, TypeScript, PHP, and Java forms through their real parser nodes and rejects arbitrary throws, broad or unguarded catches, JavaScript-family imported error aliases shadowed by parameter/local/iteration/catch bindings, multiple handlers, checked exceptions, retry/rethrow, resources, and `finally`/`ensure`/`else`. Backend coverage reparses all five generated targets, checks target guard spelling, rejects PHP assigned-only special names in catch bindings, validates caller-authored shapes before allocation, and requires deterministic `UNSUPPORTED_CAPABILITY` from every other registered target. Registry and provenance specs lock the public `typedErrors` flag, error/catch symbols, source roles, and rich/v3 mapping behavior.
56
+ Task 011 uses eight focused specs. Semantic validation covers exact nominal types, string-only construction, abrupt raise, exact handler consumption, unmatched and handler-originated propagation, immutable scoped catch bindings, catch-entry invalidation of optional-presence facts after protected-body assignments, message-only access, unreachable handlers, and fixed-point direct-call effects without checked signatures. Frontend coverage parses the canonical Ruby, JavaScript/JSDoc, TypeScript, PHP, and Java forms through their real parser nodes and rejects arbitrary throws, broad or unguarded catches, JavaScript-family imported error aliases shadowed by parameter/local/iteration/catch bindings, union or default handlers, checked exceptions, Ruby `retry`/`redo`/`else`/rescue modifiers, resources, and any deviation from the exact JavaScript/TypeScript guarded dispatch chain. Backend coverage reparses all five generated targets, checks target guard spelling, rejects PHP assigned-only special names in catch bindings, validates caller-authored shapes before allocation, and requires deterministic `UNSUPPORTED_CAPABILITY` from every other registered target. Registry and provenance specs lock the public `typedErrors` flag, error/catch symbols, source roles, and rich/v3 mapping behavior. The handling-extension spec locks the ordered multi-catch, `finally`/`ensure`, and rethrow extension across the cohort: one semantic meaning from each language's exact native form, generation and reparse for all five targets, handler-body raises escaping past remaining sibling handlers, completion-block interaction with return/raise and nested try, rethrow of a lexically visible outer catch binding, `UNREACHABLE_HANDLER` for a handler shadowed by an earlier handler of the same identity, and multi-file program preservation.
57
57
 
58
58
  The multi-file runtime spec parses five caller-supplied TypeScript modules, preserves both exported `module#error:0` identities plus a transitive two-hop function effect through every generated project and reparse, and executes PHP, Ruby, Node, strict TypeScript through `tsc` plus Node, and Java through `javac` plus `java`. Its exact `ok\nbad\ndeep\n` output proves the non-throw path, one exact catch, and an unmatched error raised by the inner handler propagating to the outer exact handler. Missing commands fail; no test skips, downloads, substitutes, or scans source text.
59
59
 
@@ -65,10 +65,12 @@ npx velocious-test spec/typed-error-frontends.spec.js
65
65
  npx velocious-test spec/typed-error-backends.spec.js
66
66
  npx velocious-test spec/typed-error-language-registry.spec.js
67
67
  npx velocious-test spec/typed-error-provenance-and-mapping.spec.js
68
+ npx velocious-test spec/typed-error-handling-extension.spec.js
68
69
  LANG=C.UTF-8 LC_ALL=C.UTF-8 npx velocious-test spec/typed-error-multifile-runtime.spec.js
70
+ LANG=C.UTF-8 LC_ALL=C.UTF-8 npx velocious-test spec/typed-error-handling-extension-runtime.spec.js
69
71
  ```
70
72
 
71
- Task 011 adds no checked exceptions/effects, arbitrary thrown values, multiple/union/broad catches, `finally`/`ensure`, async rejection, result types, non-original-five lowering, standard-library portability, pairwise rewrites, publication, or deployment behavior. Task 013 remains the historical Tasks 001–008 compatibility gate.
73
+ Task 011's handling extension adds ordered multi-catch, `finally`/`ensure`, and explicit rethrow for the original five and still adds no checked exceptions/effects or `throws` signatures, arbitrary thrown values, union/broad/default catches, Ruby `retry`/`redo`/`else`/rescue modifiers, try-with-resources, async rejection, result types, non-original-five lowering, standard-library portability, pairwise rewrites, publication, or deployment behavior. Task 013 remains the historical Tasks 001–008 compatibility gate.
72
74
 
73
75
  ## Task 012 type parameters and generic declarations
74
76
 
@@ -379,7 +381,7 @@ Task 016 extends the same fail-loud discovery and staged runner to configured Py
379
381
 
380
382
  Task 017 adds the distinct ordered `restore` stage and configured .NET SDK 10 discovery. C# acceptance generates exactly `Program.cs` and `Semantifold.csproj`, then invokes `dotnet restore Semantifold.csproj --source . --no-cache --force --disable-parallel --nologo`, `dotnet build Semantifold.csproj --configuration Release --no-restore --nologo --warnaserror`, and `dotnet exec bin/Release/net10.0/Semantifold.dll` as separate stages. Tests set the .NET telemetry/first-run/no-logo/invariant variables and an isolated absolute `NUGET_PACKAGES` path. They require exact exit/stdout and no warning output. Two fresh directories must produce identical ordered artifact text/mappings/provenance and identical `Semantifold.dll`, `.deps.json`, and `.runtimeconfig.json`; path-bearing restore bookkeeping under `obj/` is intentionally not compared. Missing .NET, restore dependency/network demand, warnings, SDK major mismatch, or nondeterministic compiler output fails without a skip.
381
383
 
382
- Task 024 adds configured Go 1.26.x discovery restricted to Linux/amd64; the patch release is read from the discovered toolchain rather than fixed by runtime tests. Acceptance obtains `GOROOT` from that exact `go`, requires the companion `gofmt` beside the configured or discovered executable to resolve to the same `GOROOT/bin/gofmt`, and checks `gofmt -d` before native execution. Each dependency-free generated module is run with `GOTOOLCHAIN=local`, `GOPROXY=off`, `GOSUMDB=off`, `GOVCS=off`, `CGO_ENABLED=0`, `GOENV=off`, `GOWORK=off`, `GOOS=linux`, `GOARCH=amd64`, and isolated HOME/GOPATH/build/module caches. The short-lived discovery fixture also writes exact `off` mode into its isolated Go telemetry directory before invoking the tool, then asserts that the mode file is the only telemetry state: Go 1.26 otherwise starts an unawaited uploader sidecar even in default `local` mode, which can recreate `upload/` while the fixture root is being removed. Cleanup errors remain fatal. Stages run offline `go build`, mandatory `go vet`, and `go run`, while compiler-only differential cases use the matching `go tool compile`. Tests require exact status/stdout, unchanged `go.mod`/`main.go`, no `go.sum`/`go.work`/vendor tree, and byte-identical binaries and output in two fresh directories. Missing, wrong-minor, wrong-architecture, mismatched-gofmt, download, cgo, workspace, vet, formatting, nondeterminism, telemetry leakage, or cleanup evidence fails without a skip.
384
+ Task 024 adds configured Go 1.26.x discovery restricted to Linux/amd64; the patch release is read from the discovered toolchain rather than fixed by runtime tests. Acceptance obtains `GOROOT` from that exact `go`, requires the companion `gofmt` beside the configured or discovered executable to resolve to the same `GOROOT/bin/gofmt`, and checks `gofmt -d` before native execution. Each dependency-free generated module is run with `GOTOOLCHAIN=local`, `GOPROXY=off`, `GOSUMDB=off`, `GOVCS=off`, `CGO_ENABLED=0`, `GOENV=off`, `GOWORK=off`, `GOOS=linux`, `GOARCH=amd64`, and isolated HOME/GOPATH/module caches over one content-addressed run-scoped Go build cache, which is removed when the test process exits. The short-lived discovery fixture also writes exact `off` mode into its isolated Go telemetry directory before invoking the tool, then asserts that the mode file is the only telemetry state: Go 1.26 otherwise starts an unawaited uploader sidecar even in default `local` mode, which can recreate `upload/` while the fixture root is being removed. Cleanup errors remain fatal. Stages run offline `go build`, mandatory `go vet`, and `go run`, while compiler-only differential cases use the matching `go tool compile`. Tests require exact status/stdout, unchanged `go.mod`/`main.go`, no `go.sum`/`go.work`/vendor tree, and byte-identical binaries and output in two fresh directories. Missing, wrong-minor, wrong-architecture, mismatched-gofmt, download, cgo, workspace, vet, formatting, nondeterminism, telemetry leakage, or cleanup evidence fails without a skip.
383
385
 
384
386
  Task 023 adds exact Kotlin/JVM 2.4.20 discovery on pinned OpenJDK 25 Ubuntu runtimes. Ubuntu26.04 development uses exact `25.0.4.1+1-1~26.04.4`; Ubuntu24.04 TensorBuzz uses exact `25.0.4.1+1-1~24.04.4`. Bootstrap and discovery accept only those two qualified compiler-reported package identities, never arbitrary output. Original and generated `Program.kt` files compile with `-language-version 2.4 -api-version 2.4 -jvm-target 25 -Werror -include-runtime` into runnable `Program.jar` files, then execute through the exact `java25` discovery contract. Every process uses a fresh directory and exact status/stderr/UTF-8 stdout assertions; no test downloads a compiler or dependency.
385
387
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semantifold",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Language-neutral semantic code analysis, transformation, generation, and execution toolkit",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -478,7 +478,8 @@ function blockExcluded(block) {
478
478
  }
479
479
  const nested = statement.kind == "IfStatement" ? [statement.consequent, ...(statement.alternate ? [statement.alternate] : [])] :
480
480
  statement.kind == "ForEachStatement" || statement.kind == "ForEachMapStatement" || statement.kind == "WhileStatement" ? [statement.body] :
481
- statement.kind == "TryStatement" ? [statement.body, statement.catchBody] : []
481
+ statement.kind == "TryStatement" ? [statement.body, ...statement.handlers.map((handler) => handler.catchBody),
482
+ ...(statement.finallyBody ? [statement.finallyBody] : [])] : []
482
483
  for (const child of nested) {
483
484
  const found = blockExcluded(child)
484
485
  if (found) return found
@@ -391,9 +391,15 @@ function emitStatement(writer, statement, indent, path) {
391
391
  writer.synthetic(");\n", "error construction close", [statement.error], [`${path}/error`])
392
392
  return
393
393
  }
394
+ if (statement.kind == "RethrowStatement") {
395
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
396
+ writer.synthetic(" ", "throw spacing", [statement], [path])
397
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
398
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path})
399
+ writer.synthetic("\n", "line break", [statement], [path])
400
+ return
401
+ }
394
402
  if (statement.kind == "TryStatement") {
395
- const caught = writer.errorForId(statement.catchType.declarationId)
396
-
397
403
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
398
404
  writer.synthetic(" ", "try spacing", [statement], [path])
399
405
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -401,18 +407,31 @@ function emitStatement(writer, statement, indent, path) {
401
407
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`)
402
408
  writer.synthetic(indent, "indentation", [statement], [path])
403
409
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
404
- writer.synthetic(" ", "catch spacing", [statement], [path])
405
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
406
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
407
- writer.mapped(writer.errorNameForId(statement.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
408
- writer.synthetic(" ", "catch binding spacing", [statement.catchBinding], [`${path}/catchBinding`])
409
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
410
- writer.synthetic(") ", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
411
- writer.mapped("{", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
412
- writer.synthetic("\n", "line break", [statement], [path])
413
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`)
414
- writer.synthetic(indent, "indentation", [statement], [path])
415
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
410
+ for (const [index, handler] of statement.handlers.entries()) {
411
+ const handlerPath = `${path}/handlers/${index}`
412
+ const caught = writer.errorForId(handler.catchType.declarationId)
413
+
414
+ writer.synthetic(" ", "catch spacing", [handler], [handlerPath])
415
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
416
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
417
+ writer.mapped(writer.errorNameForId(handler.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
418
+ writer.synthetic(" ", "catch binding spacing", [handler.catchBinding], [`${handlerPath}/catchBinding`])
419
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
420
+ writer.synthetic(") ", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
421
+ writer.mapped("{", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
422
+ writer.synthetic("\n", "line break", [handler], [handlerPath])
423
+ emitBlock(writer, handler.catchBody, `${indent} `, `${handlerPath}/catchBody`)
424
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
425
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
426
+ }
427
+ if (statement.finallyBody) {
428
+ writer.synthetic(" ", "finally spacing", [statement], [path])
429
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
430
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
431
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`)
432
+ writer.synthetic(indent, "indentation", [statement], [path])
433
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
434
+ }
416
435
  writer.synthetic("\n", "line break", [statement], [path])
417
436
  return
418
437
  }
@@ -427,10 +427,14 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
427
427
  writer.synthetic(")\n", "error construction close", [statement.error], [`${path}/error`])
428
428
  return
429
429
  }
430
+ if (statement.kind == "RethrowStatement") {
431
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
432
+ writer.synthetic(" ", "throw spacing", [statement], [path])
433
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
434
+ writer.synthetic("\n", "line break", [statement], [path])
435
+ return
436
+ }
430
437
  if (statement.kind == "TryStatement") {
431
- const caught = writer.errorForId(statement.catchType.declarationId)
432
- const targetName = writer.errorNameForId(statement.catchType.declarationId)
433
-
434
438
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
435
439
  writer.synthetic(" ", "try spacing", [statement], [path])
436
440
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -438,21 +442,15 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
438
442
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`, canonicalizeZero)
439
443
  writer.synthetic(indent, "indentation", [statement], [path])
440
444
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
441
- writer.synthetic(" ", "catch spacing", [statement], [path])
442
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
443
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
444
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
445
- writer.synthetic(") {\n", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
446
- writer.synthetic(`${indent} if (!(`, "exact catch guard", [statement], [path])
447
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
448
- writer.synthetic(" instanceof ", "exact catch guard", [statement], [path])
449
- writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
450
- writer.synthetic(`)) {\n${indent} throw `, "unmatched error propagation", [statement], [path])
451
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
452
- writer.synthetic(`\n${indent} }\n`, "unmatched error propagation", [statement], [path])
453
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`, canonicalizeZero)
454
- writer.synthetic(indent, "indentation", [statement], [path])
455
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
445
+ if (statement.handlers.length > 0) emitJavaScriptHandler(writer, statement, 0, indent, path, canonicalizeZero)
446
+ if (statement.finallyBody) {
447
+ writer.synthetic(" ", "finally spacing", [statement], [path])
448
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
449
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
450
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`, canonicalizeZero)
451
+ writer.synthetic(indent, "indentation", [statement], [path])
452
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
453
+ }
456
454
  writer.synthetic("\n", "line break", [statement], [path])
457
455
  return
458
456
  }
@@ -505,6 +503,73 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
505
503
  writer.synthetic("\n", "line break", [statement], [path])
506
504
  }
507
505
 
506
+ /**
507
+ * Emits one ordered JavaScript catch handler with its exact nominal guard.
508
+ *
509
+ * JavaScript has no sibling catch clauses, so handler k > 0 hangs off handler k - 1's
510
+ * unmatched rethrow inside a nested try. Each handler body sits outside its own guard,
511
+ * so a raise from one handler body escapes outward instead of being caught by a later
512
+ * sibling handler, exactly like native ordered multi-catch.
513
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
514
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
515
+ * @param {number} index - Zero-based ordered handler index.
516
+ * @param {string} indent - Leading indentation for this catch clause.
517
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
518
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
519
+ * @returns {void}
520
+ */
521
+ function emitJavaScriptHandler(writer, statement, index, indent, path, canonicalizeZero) {
522
+ const handler = statement.handlers[index]
523
+ const handlerPath = `${path}/handlers/${index}`
524
+ const bodyIndent = `${indent} `
525
+ const caught = writer.errorForId(handler.catchType.declarationId)
526
+ const targetName = writer.errorNameForId(handler.catchType.declarationId)
527
+
528
+ writer.synthetic(" ", "catch spacing", [statement], [path])
529
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
530
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
531
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
532
+ writer.synthetic(") {\n", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
533
+ writer.synthetic(`${bodyIndent}if (!(`, "exact catch guard", [handler], [handlerPath])
534
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
535
+ writer.synthetic(" instanceof ", "exact catch guard", [handler], [handlerPath])
536
+ writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
537
+ writer.synthetic(")) {\n", "exact catch guard", [handler], [handlerPath])
538
+ if (index + 1 < statement.handlers.length) {
539
+ emitJavaScriptDispatch(writer, statement, index, indent, path, canonicalizeZero)
540
+ writer.synthetic(`${bodyIndent}} else {\n`, "matched error handling", [handler], [handlerPath])
541
+ emitBlock(writer, handler.catchBody, `${bodyIndent} `, `${handlerPath}/catchBody`, canonicalizeZero)
542
+ writer.synthetic(`${bodyIndent}}\n`, "matched error handling close", [handler], [handlerPath])
543
+ } else {
544
+ writer.synthetic(`${bodyIndent} throw `, "unmatched error propagation", [handler], [handlerPath])
545
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
546
+ writer.synthetic(`\n${bodyIndent}}\n`, "unmatched error propagation", [handler], [handlerPath])
547
+ emitBlock(writer, handler.catchBody, bodyIndent, `${handlerPath}/catchBody`, canonicalizeZero)
548
+ }
549
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
550
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
551
+ }
552
+
553
+ /**
554
+ * Emits the nested re-dispatch of one unmatched caught value to the remaining ordered handlers.
555
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
556
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
557
+ * @param {number} index - Index of the handler whose value is re-dispatched.
558
+ * @param {string} indent - Leading indentation for this dispatch try.
559
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
560
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
561
+ * @returns {void}
562
+ */
563
+ function emitJavaScriptDispatch(writer, statement, index, indent, path, canonicalizeZero) {
564
+ writer.synthetic(`${indent} try {\n`, "ordered handler dispatch", [statement], [path])
565
+ writer.synthetic(`${indent} throw `, "ordered handler dispatch", [statement], [path])
566
+ writer.mapped(statement.handlers[index].catchBinding.name, {
567
+ mappingKind: "exact", node: statement.handlers[index].catchBinding, path: `${path}/handlers/${index}/catchBinding`, role: "name"
568
+ })
569
+ writer.synthetic(`\n${indent} }`, "ordered handler dispatch", [statement], [path])
570
+ emitJavaScriptHandler(writer, statement, index + 1, `${indent} `, path, canonicalizeZero)
571
+ }
572
+
508
573
  /**
509
574
  * Emits one JavaScript local statement and its JSDoc type carrier.
510
575
  * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.