- A guarded declaration of a name the CommonJS wrapper defines (`var
exports = exports || {}`, and `module`, `require`, `__filename`,
`__dirname`, `arguments`) reads the wrapper's value, not undefined, so
it keeps its text instead of becoming a fresh object.
- The write scan counts destructuring assignments, and destructuring
for-in/for-of targets, as writes, so a hoisted function that sets the
namespace before its guarded declaration keeps the declaration as it
is.
- An exported `var` that a later declaration of the same name sets
again is not settled at the export: importers would read the
redeclared value, where Node copies the first one into
module.exports. It stays refused (SC1090), as before. The npm case
var-export-redeclared pins it.
`JSON.parse(text) as T` for a discriminated union of records took the
checked-dynamic route: it parsed the text into a dynamic tree, matched
and copied that tree into records, then freed it. The one-pass schema
parser now builds these unions directly, as record members, array
elements and whole documents. For an object, a shallow scan of its
members finds the discriminant's value, which selects the one record arm
that holds that literal, and the object is parsed with that arm's
schema. An object with a missing, non-string or unknown discriminant, or
any member the arm rejects, declines, and the checked route then gives
the result or the error as before.
The compiler admits unions whose record arms each name their string
literals, every literal naming one arm, plus null and undefined arms.
Since 76735a8d9 (#1975), a `super.m()` call in a JavaScript class method
whose `this` is the call-site receiver reflected the base class's whole
prototype and read `m` from it. An instance's member lookup first tests
its property bag, which inherits from its class's prototype data, so once
the base's prototype was reflected, `child[k]` for a method the subclass
overrides found the base's method there: `value[name](arg)` on a subclass
instance called the base method.
While no code reads a prototype method by name, `super.m()` now calls the
declaring class's receiver-independent method value with the receiver, as
the method's [[HomeObject]] lookup answers, without reflecting the base
prototype. Programs that read prototype methods by name keep the
reflected lookup.
An unchanged rebuild that kept its output still selected the runtime pack's
artifacts first, reading the stored selection, and computed the identity of
every selected artifact to open the executable cache.
- The executable cache key names the pack by its package.json text, the
manifest's identity (its text on darwin), and the link features and
flavor. Together they fix which artifacts the selection names.
- The cache selects the artifacts only when it needs their identities, in
restore() and trace(). keep() checks the recorded entry, which lists them.
- A build that does not keep or restore its executable selects the pack
before code generation, as before.
A read site's cache only remembered properties it found on the receiver
or its first prototype. Reads of properties an object lacks, which
validation libraries make constantly (zod reads ctx.skipChecks,
payload.memo, x.aborted and _zod.optin on every check), walked the whole
shape chain and then repeated the lookup on the generic path every time:
7.9 million such reads in one zod benchmark run.
- Read sites call a new probe, scr_js_get_probe, that answers undefined
for a key the receiver and its chain of plain shaped objects (at most
one prototype, ending in the default prototype) lack, and caches that
answer. The entry holds while the receiver's shape, its prototype link,
and that prototype's shape are unchanged. Its tag and placeholder
holder make the inline entry-0 test in compiled code miss, so the
answer always comes from the probe. Method reads keep
scr_js_load_probe, so a missing method still reaches the slow path and
its "is not a function" error. Names of Object.prototype members are
never cached as absent.
- Receivers of one shape with different prototypes (zod's internals
objects) each keep an entry instead of replacing one another.
- A site whose four-way record is full consults a per-thread table keyed
by shape and key before walking shapes, and fills it.
instanceof Promise on values that are never promises now compiles to
false after the operand is evaluated, so the diagnostics fixture no longer
lists it as refused. 8326-never-promise-instanceof covers records, arrays,
dates, collections, regular expressions, byte arrays and unions with null,
and a call operand that still runs.
8179-tls-reject-unauthorized-values.mjs called the resolve function of an
untyped new Promise() with no argument. tsgo types that function as taking
one argument and reported SC0001 for each call, while tsc-ts accepted them,
so the checker-identity comparison failed. The calls now pass undefined.
In a program that can start a worker, a typed-array loop version's guard
requires thread-owned storage, so every view of a SharedArrayBuffer runs
the version's checked copy. That copy gave its loops no speculative or
int32 kind copies, on the assumption that it rarely runs, so loops over
shared views, such as k-nucleotide's frequency count in every worker, ran
only the double copy. A version whose guard requires thread-owned
storage now keeps those copies in its checked copy.
- toString("latin1"): counts the high bytes eight at a time, copies
words of ASCII bytes, and writes other bytes from a table of their
UTF-8 pairs without branches. ASCII-only results are marked ASCII, and
other results record their UTF-16 length (one unit per byte), so
`.length` does not walk the string.
- Buffer.from(text, "latin1" | "ascii" | "binary"): copies text known to
be ASCII, copies words of ASCII bytes, and decodes eight bytes of one-
and two-byte sequences in one word, storing each output byte without
branches. Longer sequences take the previous path.
- Buffer.from(text, "hex"): reads digit values from a table.
- toString("hex") and toString("base64"/"base64url") mark their results
ASCII, so a later decode, length or index read skips the scan for
non-ASCII bytes.
The native compiler now starts with the cycle collector's nursery trigger
at 1024 new candidates instead of 256. scr_cyc_set_nursery_threshold sets
the trigger that SCR_CYCLE_THRESHOLD sets, on the pattern of
scr_cyc_set_growth_cap; the environment variable still takes precedence.
The compiler sets it from a constructor in its host object, before its
modules initialize. Set from main, the trigger leaves the build's
collection work where it was: the cost depends on the collector's first
passes. Programs keep the default trigger.
A cold tsc-ts build takes 4% less wall time (12 paired runs) and its peak
RSS goes from 1.41 GB to 1.31 GB. The program's IR is unchanged.
A Map/Set lookup's integer key view was cached for the function by the key
operand's name. When the view was a conversion emitted at a lookup in a
branch, a later lookup of the same operand (a wide integer constant) reused
it from a block the conversion does not dominate, and LLVM rejected the
module. Only the value's own integer or uint32 view and integral literals
are cached now.
- A function's results include undefined when a call can run off the end
of its body or reach a bare return, so the overlay's return type keeps
undefined and a caller's branch on it stays reachable. `new` of an ES5
constructor ignores primitive results, and functions called with `new`
get no @returns tag.
- Package JSDoc tags are neutralized only in comments: string, template
and regular expression literals and JSX text keep their text.
- A branch replaced as unreachable keeps the var names it declares that
its scope declares nowhere else, so references outside it read
undefined. A branch that declares a function in a block is kept.
- The shape-overlay-facts npm case covers all three; four files of the
overlay change are formatted.
The inference reads types while it runs: resolving a candidate's
constructor through a destructured require (const { TextDecoder } = util)
asks for the namespace's type, which asked for the inferred collection
types again and restarted the inference until the stack overflowed. The
lowerer now stores an empty table before inferring, so reads during the
inference see no inferred types, and stores the result after.
With the shape overlay on, a first round of structural rewrites runs before
the typed overlay (shape-inference/static-objects.ts, stage 2b of
DESIGN-constant-objects.md), proven by the constant object graphs:
- An ES5 constructor stored by a top-level statement whose prototype is a
constant object of plain methods becomes a class declaration with those
methods, when the program cannot tell: the function is only ever
constructed and never escapes, its prototype is never replaced, nothing
enumerates instances with for-in or reflects on the prototype or the
function, nothing reads their names or sources, the bodies are
strict-mode safe, and only definitions run between the constructor and
its last method.
- A constant namespace member holding a `this`-free function expression
becomes a function declaration, and reads of the member in the same file
whose receiver is exactly that constant object refer to it directly.
The rounds rerun the analysis on the rebuilt program, the overlay now also
covers the program's own JavaScript files, and the build report counts the
rewrites. A JavaScript switch whose case tests lower to another type than
the discriminant compares with === instead of being refused.
Namespace benchmark (n=20000): 1.55 s -> 0.42-0.47 s, output identical to
Node (typed TypeScript twin 85 ms, Node 21 ms).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two refusals kept pre-module JavaScript packages from compiling:
- `var ns = ns || {}` (and `?? {}`): the read in the variable's own
initializer made the checker type the namespace `any`, and requirers
then failed to reach the binding (SC1030). In a module, the read sees
the hoisted `undefined` whenever nothing else writes the variable, so a
new package-source normalization respells the declaration as its
default (`var ns = ({})`) when no other declaration, assignment, update,
function, class, parameter or local of that name exists in the file and
no earlier top-level statement mentions it.
- `module.exports = { ns }` with a `var` binding was refused as exporting
a mutable binding by reference. A `var` (or `let`) whose initialized
declaration precedes the export statement at top level and that nothing
reassigns exports exactly the value Node copies, so it now aliases like
a const.
The namespace benchmark (results/design-shape-inference/nsbench) now
compiles and matches Node; the new npm case legacy-namespaces pins a
namespace library with guarded declarations, constructor functions with
prototype methods, enums, a revealing module and a handler registry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shape overlay (DESIGN.md, approved route) rewrites the JavaScript of
opted-in npm packages with JSDoc types the shape analysis proves: record
typedefs for non-escaping literal sites (optional fields for spread
sources), class field, parameter, return and local types, receiver casts,
and branches the analysis proved unreachable replaced by checks. The
lowering trusts only annotations carrying the overlay's marker; package
JSDoc stays a hint (its type tags are neutralized in overlaid files).
- Inferred-fact checks: every checked conversion lowered in an overlaid
file records its site, and a failure throws a distinct internal error
("scriptc internal error: an inferred type was contradicted at <file:line>
… This is a bug in scriptc's shape analysis, not in the program's data")
naming the opt-outs; SCRIPTC_SHAPE_CHECK_LOG makes checks log instead
(validation mode).
- Opt-outs: `--no-shape-overlay`, and `"scriptc": {"packages": {"<name>":
{"shapes": false}}}` per package.
- Build report: SC6103 counts the declarations typed, the checks inserted
and the branches replaced.
- `x instanceof Promise` on plain data folds to false.
The overlay runs only with SCRIPTC_SHAPE_OVERLAY=1 while it is new.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two refusals kept pre-module JavaScript packages from compiling:
- `var ns = ns || {}` (and `?? {}`): the read in the variable's own
initializer made the checker type the namespace `any`, and requirers
then failed to reach the binding (SC1030). In a module, the read sees
the hoisted `undefined` whenever nothing else writes the variable, so a
new package-source normalization respells the declaration as its
default (`var ns = ({})`) when no other declaration, assignment, update,
function, class, parameter or local of that name exists in the file and
no earlier top-level statement mentions it.
- `module.exports = { ns }` with a `var` binding was refused as exporting
a mutable binding by reference. A `var` (or `let`) whose initialized
declaration precedes the export statement at top level and that nothing
reassigns exports exactly the value Node copies, so it now aliases like
a const.
The namespace benchmark (results/design-shape-inference/nsbench) now
compiles and matches Node; the new npm case legacy-namespaces pins a
namespace library with guarded declarations, constructor functions with
prototype methods, enums, a revealing module and a handler registry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A typed array such as a number[] reaches untyped code as a capsule of the
native array. A keyed read `xs[i]` there missed the inline cache, which
answers only dyn arrays, and went to the member helper, which converted
the index to a string; the capsule's key lookup then parsed the string
back into a number for every element.
Keyed sites now read an element of a native number[] capsule in place
when the index is an integer below the array's hole-free prefix, and
`length` sites read the native array's length. Other capsule elements
(holes, out-of-range and fractional indices, arrays of other element
types) are answered by the keyed probe through scr_dyn_index_get without
forming the key, or by the helper as before.
Node's afterWriteTick sets afterWriteTickInfo to null before it runs the
callbacks, whichever tick it is. The runtime cleared the queued batch
only when the running tick was that batch, so a write made from an
earlier tick's callback joined a later queued tick with the same
callback and completed ahead of nextTicks queued between them. Any
afterWrite tick now closes the batch, and such a write queues its own.
The guard proves the views disjoint for one entry of the loop. An
enclosing loop may pass other views on its next pass, such as two
buffers swapped after every pass, so one pass's write and the next
pass's read can be the same bytes under different scopes. Without a
declaration, a scope holds across the whole function, and LLVM could
apply one entry's no-alias facts to another's accesses once it unrolled
the enclosing loop.
Each fast copy now begins with llvm.experimental.noalias.scope.decl for
each of its scopes. A declared scope holds from its declaration, and
LLVM gives each copy of the declaration its own scopes when it unrolls
or duplicates the code around it.
Corpus 8182 adds ping-pong passes over swapped Float64Array and
Int32Array views; the emission test checks the declarations.