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_memberbuilds(v as Extract<typeof v, { one: unknown }>).onebecausetscrefusesv.oneon a value typed as the whole discriminated union. There is no such refusal here:Node::case_memberisv.one, exactly ascrate::emit::lua::Lua::case_memberisv.onefor the same reason. - No literal-type rewriting. TypeScript’s
castrewrites 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 holderNode::castalready puts it in. - No
noUncheckedIndexedAccessassertion.Node::indexisv[i], notv[i]!— that operator does not parse outside TypeScript, and a plain JavaScript array answersv[i]the same way regardless of what a type checker would have said about it. @externalneeds 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 everyexternal_*seam are left untouched, exactly ascrate::emit::typescriptleaves 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.
- Node
Type - A JavaScript holder, carried alongside the tree so
Node::castandNode::storeknow which of the three coercions — or none — a value needs on its way into a place.