Skip to main content

Module node

Module node 

Source
Expand description

The Node (plain JavaScript) backend of the shared codec walk.

DDS-XTypes 1.3 §7.4 from the clause, the same as runtime/c, runtime/typescript and the rest do, and none of them calls another. Where two columns disagree on an octet, one has misread the specification — which is the only reason to have a second column.

§The value model, kept from TypeScript

Plain JavaScript and TypeScript share one runtime: number is an IEEE 754 binary64 and bigint is exact and unbounded, which is a fact about the language, not about its type checker. So this column draws the same boundary crate::emit::typescript::TypeScript::scalar does — every integer to i32 and every f32/f64 is a number, a long long and an unsigned long long are bigint, a char and a wchar are a one-character string, and a long double is { octets: Uint8Array(16) } and never a number, because a binary64 would drop sixty bits of its significand at the assignment. Deriving a different model from crate::emit::zig or crate::emit::lua instead — a char as a code-point integer — would be answering a question the language has already answered the other way, through no choice this column makes.

§Where this column is simpler than TypeScript’s, and why

Every one of these follows from having no compiler holding a number, a bigint and a discriminated union’s literal types apart — not from skipping a check TypeScript’s runtime still needs:

  • No union-arm narrowing. crate::emit::typescript::TypeScript::case_member builds (v as Extract<typeof v, { one: unknown }>).one because tsc refuses v.one on a value typed as the whole discriminated union. There is no such refusal here: Node::case_member is v.one, exactly as crate::emit::lua::Lua::case_member is v.one for the same reason.
  • No literal-type rewriting. TypeScript’s cast rewrites a union label’s literal so its type matches what === narrows on; nothing here narrows a type, so a label is the value the walk handed over, in the holder Node::cast already puts it in.
  • No noUncheckedIndexedAccess assertion. Node::index is v[i], not v[i]! — that operator does not parse outside TypeScript, and a plain JavaScript array answers v[i] the same way regardless of what a type checker would have said about it.
  • @external needs nothing. Every object this column builds is already a reference — there is no pointer to take and no allocator to route it through, which is also why the trait’s defaults for every external_* seam are left untouched, exactly as crate::emit::typescript leaves them.

§Where this column still needs TypeScript’s coercion functions

Node::cast still calls {@link zdNumber}, {@link zdBigInt} and {@link zdChar} by name, and Node::store still wraps every scalar read in Node::cast before it reaches its member. Not because plain JavaScript refuses a mismatched assignment — it does not — but because the shared decode walk’s primitive_read_at casts a wchar read to crate::emit::Scalar::UInt16 before this seam ever sees it, which for a column that holds wchar in a string is the wrong holder for the member; Node::store’s cast is what turns it back. The exact defect this override closes is crates/lang-rules/src/emit/typescript.rs’s store documentation, verified afresh here rather than assumed: this runtime’s readWchar and writeWchar are one-character strings top to bottom, and the walk’s hard-coded UInt16 narrowing sits between two of the four call sites regardless of which column reads it.

Structs§

Node
The Node backend.
NodeType
A JavaScript holder, carried alongside the tree so Node::cast and Node::store know which of the three coercions — or none — a value needs on its way into a place.