flush() on a zlib stream writes a zero-length chunk that _transform
recognizes by identity and compresses with that flush kind. Since
writes of a typed array that is not a Buffer reach _write as a Buffer
over the same bytes (as Node's Stream._uint8ArrayToBuffer does), the
sentinel, created as a plain Uint8Array, reached _transform as a new
Buffer. _transform then used the default NO_FLUSH, so flush() emitted
nothing: corpus 5374-zlib-stream-flush-params decoded an empty prefix
where Node decodes "hello ".
The sentinels are Buffers, as Node's kFlushBuffers are (Buffer.alloc(0)),
so write() passes them through unchanged.
The npm fixture stream-shims, whose test expected a deferred SC1090, now
runs to a later deferred refusal, SC2020 (write completion callbacks
taking the error argument); its expectation names that code.
emitInlineAtomicsOp read its typed array with emitExpr, so every Atomics
call on a local array retained the array before the atomic instruction and
released it after, two out-of-line calls around one instruction. The
array now goes through emitBorrowedInput: an unchanged, unboxed local owns
it across the later operands and the operation, and the instruction and
scr_atomics_op only read it. Fields, globals, captures and bindings an
operand can reassign keep their own reference.
A local number[] that only compiled element operations reach, and whose
every stored element the int32 slot analysis proves an int32 (never -0),
is now stored as SCR_ELEM_I32: 4-byte int32 slots, with state bytes once
it holds a hole or undefined, as for every array. Element reads give
consumers an exact int32, so indexing with an element skips the double
round trip, and the slots take half the memory.
The runtime's dense slot accessors convert between the int32 slot and the
double, so its double-valued entry points serve int32 arrays unchanged;
growth, copies, fill and length changes use the 4-byte width.
ir/int32-arrays.ts proposes the candidates and inlines their element-reader
helper calls; the int32 slot analysis keeps the proven ones, and
selectInt32Arrays rewrites the module for exactly those, so a program
without an int32 array emits the same LLVM IR as before.
A JavaScript `var` took the checker's type of its initializer, which no
later write is checked against in a JavaScript file. js-yaml 4.1.1 failed
on every timestamp with "a 'string' value is not representable in the
target union": its `var fraction = 0` holds the fraction's digits as a
string while it pads them (`fraction = match[7].slice(0, 3)`). Every YAML
error also threw the "assignment to non-variables" fence instead of the
YAMLException, because its error mark is a `var` object literal that
gains a member (`mark.snippet = snippet(mark)`).
- A `var` whose writes the declared type cannot hold is checked-dynamic,
by the rules `let` already uses (jsBindingHasOpenWrites), and so is a
`var` whose object literal gains members or carries methods
(jsDeclarationExpandsObject, shared with `let` and `const`).
- `+=` of a string onto a number or boolean binding (`width += "px"`)
counts as such a write, for `var` and `let`.
- A JavaScript function that returns such a binding returns a
checked-dynamic value (an async one a promise of one) instead of the
initializer's type. A string binding that is only appended to stays a
string.
`C.call(...)` through a class value typed as `Base` may run a subclass's own
static `call`, which Node calls instead of the class constructor. The
constructor-call lowering now applies only when no class the receiver can
hold declares a static `call` or `apply`: the class and its bases (any
static member of that name, including accessors, fields without
initializers and computed names), and, for a receiver that may hold a
subclass, every subclass. Otherwise the call keeps its existing
diagnostic.
Since Iterable<T> values compile (#1875), two diagnostics fixtures changed:
- fromentries-aliased-entries.ts compiles and prints what Node prints:
the entries' one array stays shared by both fields. It moves to the run
corpus as 7440-fromentries-aliased-entries.ts.
- iterator-slot-fences.ts no longer reports its Iterable<number>
parameter (SC2020) and the call through it (SC2004). That part leaves
the fixture, which keeps the class-iterator fence; corpus 7380-7383
cover Iterable values.
`C.call(thisArg, ...)` and `C.apply(thisArg, args)` on a program class that
declares no static `call` or `apply` now lower as a call through the boxed
class value. It evaluates the arguments in order, applies apply's
argument-list checks, and throws `TypeError: Class constructor C cannot be
invoked without 'new'` with the runtime class name, as Node does. Before,
TypeScript reported SC2020 at compile time and JavaScript threw the
standard-library fence's Error at run time. This covers ES5 constructors that
call a class as their base (`function Sub() { Base.call(this); }`).
Runtime-provided classes (Error, EventEmitter, streams, EventTarget), classes
with runtime statics, spread arguments, and receivers other than a name,
`this` or a namespace member keep their existing lowering.
The fixture's unreached statement pushed values onto `{ items: [] }`, which
mapped to a `number[]` and could not hold them. Since #1881 a JavaScript
`[]` literal field is a checked-dynamic array, so the push compiles and the
test "a failed unreached JavaScript statement is always explained" found no
failed statement. The fixture now starts `items` as `[0]`, a `number[]`, so
the push of a boolean onto it still does not compile and reports SC1090 at
the same line.
When the last reference to an array dropped, the runtime ran the generic
teardown: scr_rc_destroy's depth bookkeeping, an indirect call to the array
destroy, and a loop over the element slots. Arrays of numbers, booleans and
compact ProgramLifetime references release nothing, and neither do arrays of
other ProgramLifetime instances, whose release does nothing.
- scr_arr_release frees an acyclic array without an ext directly when its
elements are numbers, booleans or compact references, or its descriptor
has SCR_ARR_OPS_RELEASE_NOTHING. Every other array keeps the full
teardown.
- The backend sets SCR_ARR_OPS_RELEASE_NOTHING in the descriptor's flags
word on 64-bit targets for ProgramLifetime element types that are not
compact, plain or as the pointer arm of a nullable union, in programs
without lifetime regions. Registration copies it into a table indexed by
the descriptor's registry id before the id is published. Descriptors
without the flag read 0 and keep the full teardown.
Corpus 7401 drops arrays of ProgramLifetime instances, numbers and booleans
through every array operation, with holes and far indices, next to arrays of
ordinary objects, and matches Node.
`for (let i = 0; i < s.length; i++) if (s[i] === " ") n++` and the same
loop over `s.charAt(i)` got no loop version: `s[i]` lowers to charAt,
and the version covered only charCodeAt reads. Each character paid the
inline helper's ASCII and bounds tests and kept its opaque fallback.
`const c = s[i]; if (c >= "a" && c <= "z") n++` also built the
character string and compared strings twice.
- The typed-array loop version covers `s.charAt(index)` like
`s.charCodeAt(index)`, and a counted loop that reads either at its
counter takes an integer counter. In the fast copy, a charAt used as a
string is the immortal one-character string for its byte, and the
equality and switch rewrites to charCodeAt read the byte.
- A comparison of `s.charAt(i)` with a one-unit literal compares the
code unit, as the equality does. An index out of range reads "",
which is below every one-unit literal; its unit read is NaN, which the
unordered predicates count as below.
- A const string local initialized from a one-argument charAt and used
only in one-unit equalities, comparisons and switches holds the code
unit instead (char-unit-locals.ts).
JSON.stringify of a value reached through `unknown` walks native records
and arrays through their live capsules. The protocol walk built a
key-string array for every record and formatted every array index with
snprintf into a fresh string, only to look the member up again by that
key. A JSON API response of a few records spent most of its
serialization time there.
Without a replacer, walk a live record by field index over a snapshot of
its present fields (its keys are the immortal field names) and a live
array by element index, forming an index string only for a toJSON call
that receives it. Members are still read as scr_json_member reads them,
so callbacks that mutate or materialize the container see the same
results. Index keys elsewhere use the integer formatter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`globalThis as typeof globalThis & { key?: T }` (the usual global
singleton spelling) matched the WebAssembly cast lowering, because the
target inherits the global's own WebAssembly member, and produced a fresh
object holding only WebAssembly on every evaluation. Writes through the
view were lost, so a `g[key] ??= new Repo()` singleton was rebuilt on
every call and its state never persisted.
Take the WebAssembly view only when the target's member is not the
global's own, and map an intersection that contains the intrinsic
`typeof globalThis` to the global object (dyn), as `typeof globalThis`
alone already maps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
for-of over an iterable union read its array arm without checking that it
had one, so iterating a `Set<number> | Generator<number, void, undefined>`
crashed the compiler; a union without an array arm now runs the generator
loop only. A generator with a non-void return channel adapted into a
required Iterable<T> slot but not into an optional or nullable one, where
the conversion failed with SC2003; the slot conversion now accepts
iterable unions with undefined or null arms beside the iterable arms.
Two JavaScript object literal mappings from #1799 broke packages.
A property initialized to `[]` has the checker type `never[]`, and never's
element maps to f64. A literal with such a property builds as a
checked-dynamic object when it lowers on its own. A literal nested in one
that maps to a record (`{ common: { issues: [] }, path: params?.path || [] }`,
zod 3's parse context) lowers against its record type, so the property was a
`number[]`. `ctx.common.issues.push(issue)` then threw "TypeError: expected
number at $, got object", where Node pushes the record. Such a property now
maps to the checked-dynamic array, as a property initialized to `undefined`
or `null` maps to the checked-dynamic value.
That `undefined`/`null` rule also matched the properties the checker adds
when it normalizes a union of literals: `{ _tag, value } | { _tag, error }`
gains an optional `error?: undefined` and `value?: undefined`. Mapping those
to the checked-dynamic value let `{ _tag, value }` fit both arms, and Effect
4's CLI parser (`consumeFlagValue`) failed with SC2003 "union conversion has
no unambiguous layout mapping". The rule now skips optional properties, so
they keep their unit-only type and each literal converts to its own arm.
In a compiled program an opted-in package is typed from its JavaScript, so a
type computed from members only its declarations have (zod's
z.infer<typeof Schema>) is any. An interface extending it mapped to a record
of its own declared fields, and reads of inherited fields and spreads into it
were refused. It is now checked-dynamic, like the base.
The zod3-infer npm case uses z.infer, z.input and z.output across objects,
interfaces extending inferred types, spreads, optional and nullable fields,
unions, discriminated unions, transforms, defaults, .extend and arrays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Iterable<T> maps to a union of the sources that keep their own
representation (T[], Set<T>, Map<K, V> for [K, V] entries, string) and a
generator over T that holds every iterator. An array stays the same
multi-pass object; a generator stays single-pass. for-of gives the array
arm its own array loop, so iterating an Iterable that holds an array runs
at array speed. Spread, Array.from, the Set/Map constructors and
Object.fromEntries consume it; optional Iterables narrow into it;
generator functions may declare an Iterable return. Class iterables and
typed arrays are refused at the conversion with a named diagnostic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With while-loop versions, `while (pos < end && s.charCodeAt(pos) === 32)
pos++` took a version whose guard could never hold. The counter scan
records only typed-array accesses, so the string read fell back to the
statically ranged check. For a counter held in an int32 slot, that check
asks for a length above 2^31 - 2. The loop paid for the guard and ran its
checked copy.
The counter scan now records `s.charCodeAt(i)` and `s.charAt(i)` of a
string like typed-array reads. Their guard compares the counter's bound
with the string's length, as for a typed array. Corpus 7302 adds while
loops over counters below a string's length.
An object literal that spreads an `any` value into a typed slot
(service.list({ ...c.req.valid("query"), offset: 0 }) where the validated
value is untyped) was refused, because only known record shapes spread.
TypeScript types such a literal `any` and the copy holds whatever own
properties the source has at runtime, so it now builds as a
checked-dynamic object and checks into the expected record like any other
`any` value assigned to that slot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A keyed read of a record whose declared fields have different types, with
a key the checker types as keyof the record (row[key] in a loop over
Object.keys(patch) as (keyof Row)[]), was refused: the read needed one
common field type. The checker already types such a read as the union of
the field types; when every declared field surfaces in that union, the
existing per-field dispatch wraps each hit into its arm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>