/** * emit-shared-debox.ts — the SINGLE, language-neutral source of truth for compiling a strict de-box * from an SCP-resolved type. * * WHY THIS FILE EXISTS * -------------------- * A strict de-box turns a producer's wire payload into the STATICALLY DECLARED native type, checking * every value against that type and producing a structured Error Value on a mismatch. SCP resolves the * whole type at parse time — every annotation point is a finite, ACYCLIC `TypeRef` tree (a named type * can never contain itself; see typed.ts `structuralKey`, "型記法は非再帰"). Because the type is fully * known at generation time, the de-box is a TOTAL function of the resolved type: for every position * (node-result top, list element, map value, record field, any nesting) a decode can always be * generated — scalar → probe + type-check, opt → present/absent, named → recurse each field, arr → * recurse each element, map → recurse each value. There is NO shape that "cannot be de-boxed": the only * legitimate non-generation is a type SCP could not resolve, and that fails at type resolution, never * here. * * Historically the decision "which shapes de-box, and how" was scattered across per-POSITION predicates * (a `recordTop` for node results, a `…ElemEligible` for map/fanout elements, a nested-value recursion, * and a fail-close gate) DUPLICATED per language. They disagreed by construction (a top handled * `opt(named)` while an element did not), so every new shape had to be added to all of them and a miss * silently fell through to a fail-close — the recurring "de-box was missing here". This module removes * that surface: {@link buildDeBoxPlan} is the ONE total walk over the grammar, producing a * language-neutral {@link DeBoxPlan} tree. Each language emitter renders that tree to FLAT, INLINE, * CONCRETE native code (no per-type helper function, no output-side recursion, no dynamic dispatch, no * dictionary) — the type is baked all the way through by the TS compiler, not deferred to the target * compiler's monomorphizer/inliner or to a runtime interface. The walk here owns STRUCTURE and * totality; the renderers own only SYNTAX. */ import { type TypePlan, type TypeRef } from "./typed.js"; /** The four concrete scalar kinds a de-box leaf checks (the grammar's `null` scalar is handled * separately — it is not a decodable datum but a producer-null marker). */ export type DeBoxScalar = "string" | "int" | "float" | "bool"; /** * Access — HOW a plan node's wire value is obtained from its enclosing wire. This is the only thing a * renderer needs to pick the right probe; the value's TYPE is carried by the plan node's kind. * - `top` the value IS the node's result wire (a {@link WireValue}); probe it with `as_*`. * - `field` a static-keyed attribute of the enclosing WireRow; probe with `probe_*(key)`. * - `elem` the current element of the enclosing WireList; probe with `elem_*(index)`. * - `mapval` the value at the current key of the enclosing WireRow (a declared `map(V)`); probe with * `probe_*(key)` where the key is the loop variable the renderer supplies. */ export type DeBoxAccess = { from: "top"; } | { from: "field"; key: string; } | { from: "elem"; } | { from: "mapval"; }; /** Common fields every plan node carries: the error labels (scp-error.md `model` / `field`), the * STATICALLY rendered expected type (Portable Type Notation — nothing walks a type at runtime), the * access, and `optDepth` (0 = required; N = wrapped in N optional layers — absent/null → the all-none * value, so a required-position miss is a structured error while an optional-position miss is None). */ interface DeBoxCommon { optDepth: number; access: DeBoxAccess; /** scp-error.md `model` — the declaring type name at this position (the enclosing named type, or the * node/field label for a top-level or collection position). */ model: string; /** scp-error.md `field` — the raw field label carried into the structured error. */ field: string; /** the rendered Portable Type Notation of the ORIGINAL ref at this position (opt() included) — * the `expectedType` literal baked into a mismatch error. */ expectedType: string; } /** WirePlan — a node in the language-neutral wire-boundary plan tree. Every SCP-resolved type maps to * exactly one of these (the walk is total); a renderer folds the tree into inline native code, either * direction: OUT (wire→typed, de-box) or IN (typed→wire, box). The two directions share ONE walk * ({@link buildWirePlan}) — the plan owns STRUCTURE + totality; each per-language renderer owns SYNTAX. * - `value` is the opaque BC-owned wire itself (bc#156, INPUT-only): box = identity (already wire), * de-box = fail-closed (a `value` at an output position is rejected at plan build). It carries no * child plan — the datum crosses the boundary unchanged. */ export type WirePlan = ({ k: "scalar"; scalar: DeBoxScalar; } & DeBoxCommon) | ({ k: "value"; } & DeBoxCommon) | ({ k: "record"; typeName: string; fields: { name: string; plan: WirePlan; }[]; } & DeBoxCommon) | ({ k: "list"; elem: WirePlan; } & DeBoxCommon) | ({ k: "map"; value: WirePlan; } & DeBoxCommon); /** DeBoxPlan — alias for {@link WirePlan}. A de-box (OUT) plan contains a `value` node ONLY when built for * an explicitly output-passthrough node (bc#164), at its top-level output or the direct element of a * top-level `arr`; otherwise a `value` is fail-closed at plan build. The de-box renderers store a `value` * node's wire AS-IS (no probe, no per-op re-box) and keep an exhaustiveness guard for the fail-closed case. */ export type DeBoxPlan = WirePlan; /** * notationOfRef — the Portable Type Notation of a TypeRef (scp-error.md), resolving a named ref to its * `obj{…}` notation via the plan. Baked as the `expectedType` literal: a rendering of the STATICALLY * declared type, so nothing walks a type at runtime. Language-neutral (shared by every emitter). */ export declare function notationOfRef(ref: TypeRef, plan: TypePlan): string; /** WireDir — the boundary crossing direction: `out` = wire→typed (de-box, a leaf RESULT), `in` = * typed→wire (box, a transport INPUT). One walk, two renderers; the direction only decides whether a * `value` node is admissible (IN = identity) or fail-closed (OUT = input-only declared type). */ export type WireDir = "in" | "out"; /** * buildWirePlan — the ONE total walk (both directions). Given an SCP-resolved `ref` at a position reached * via `access`, return the wire-boundary plan for a value of that type. Recurses over the FULL grammar at * every nesting so arbitrary shapes (opt(named), arr(arr(named)), map(opt(named)), …) are covered by * construction — no per-shape allow-list, no fail-close branch in generated code. `ctx.model` / * `ctx.field` are the error labels inherited at this position (a named type re-roots them to its own * name / field names; a collection keeps the enclosing labels — whole-field Error Value granularity). * `dir` governs the one type that is direction-asymmetric: `value` (the opaque BC wire, INPUT-only) is a * `value` plan node on the IN (box = identity) path and a fail-closed codegen error on the OUT (de-box) * path — enforcing "value is input-only; a value in an output position is rejected". * * `passthrough` (bc#164) is the ONE guarded relaxation of that OUT fail-close: a covered node EXPLICITLY * flagged output-passthrough carries an OPAQUE-WIRE output (`value` or `{arr:"value"}`) so its result stays * `WireValue` / `Vec` and feeds a downstream op-agnostic leaf with NO per-op `typed→Value` * re-box. When set, a `value` at the node's TOP-LEVEL output (or the direct element of a top-level `arr`) * is admitted as a `value` plan node (the renderer stores the wire AS-IS). It does NOT propagate into a * record field or a map value: a `value` nested inside a NON-passthrough position stays fail-closed * (no blanket relaxation, no untyped escape into the typed covered plane). The legal passthrough shapes are * exactly `value` and `{arr:"value"}` (validated at the flag's read sites — guard / interpreter / emitter). */ export declare function buildWirePlan(ref: TypeRef, access: DeBoxAccess, ctx: { model: string; field: string; plan: TypePlan; }, dir: WireDir, passthrough?: boolean): WirePlan; /** buildDeBoxPlan — the OUT (wire→typed) entry: {@link buildWirePlan} in the de-box direction. The * existing de-box call sites consume this; the walk is shared with the box (IN) direction. `passthrough` * (bc#164) admits an opaque `value` at the flagged node's top-level output (or top-level `arr` element) — * the renderer stores that wire AS-IS. When unset (the default), a `value` in an output position stays * fail-closed exactly as before. */ export declare function buildDeBoxPlan(ref: TypeRef, access: DeBoxAccess, ctx: { model: string; field: string; plan: TypePlan; }, passthrough?: boolean): DeBoxPlan; export {}; //# sourceMappingURL=emit-shared-debox.d.ts.map