The temps a regex replace or replaceAll consumed (the subject that a
previous substitution in a chain produced, a computed replacement) were
released at the end of the statement. A chain of substitutions over a
large string therefore held every intermediate result until the last one
returned. The operands now live in a frame of their own that is released
right after the call, so a chain holds at most its current subject and
result.
isFallbackDtsPath runs for every type lowering maps (isStdlibFile), and
since the fallback declarations moved into per-module files (#2256) it
asked declarationPath for the fallback module directory each time. Without
an installed declaration root, each answer resolved
@scriptc/compiler/scriptc-node-fallback.d.ts through Node's module
resolver. Lowering three.js's buffers-animation fixture went from 6.3 s to
43 s. declarationPath now keeps its answers by name until the declaration
root changes.
Stage N2 of the localized-failure design (canonical type normalization).
At localized failure's commit points, before a failing type degrades to
its pushed-down or whole-slot dynamic form, its canonical form maps
through mapCanonical under the current bindings. When that gives a static
IR type, the type maps exactly: no dynamic slot, no SC6104. Opaque and
unbound forms, and forms that map only to the checked dynamic, degrade as
before. The coverage remainder re-derives canonical forms in its own
tables. Because it runs only at commit points, a program that compiles
today never consults it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once shadow mode imports the normalizer, the self-hosted frontend lowers
it, and it cannot compare union-typed canonical nodes by identity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each @scriptc/cli-<platform> package bundled its platform's LLVM helper
and runtime pack, which are also published as @scriptc/llvm-<platform> and
@scriptc/runtime-<platform>. The CLI packages now depend on those packages
at the same version (workspace:*, which pnpm pack writes as the exact
version) and leave them out: cli-linux-x64-gnu goes from 115 MB to 60 MB
packed.
- build-native-cli.mts --platform-packages, which each CLI package's
build:native passes, writes a manifest without llvm_package, runtime_pack
and runtime_packs.
- loadNativeToolchain resolves a missing helper or host runtime pack as
Node resolves the package from the compiler's directory: nested in the
platform package, beside it (hoisted, or pnpm's virtual store) or higher
up. It requires the compiler's version, and an absent or mismatched
package fails with an error that names it.
- installNativeCli records where the package manager put both packages in
the installed manifest, with the same checks.
- verify-native-cli.mjs requires the exact dependencies of a platform
package and checks the resolved packages' versions.
- package-native-cli.mjs bundles the helper and runtime pack into the
standalone archives, which stay self-contained.
- smoke-native-install.mjs installs the helper and runtime pack tarballs
with the platform package; the bootstrap's exported package drops its
bundled copies first.
- release.yml publishes the CLI packages only once every helper and
runtime pack package resolves on npm, and scriptc only once the CLI
packages resolve, each wait at most 25 minutes. A rerun skips the
uploads npm already accepted and waits again.
Stage N1 of the localized-failure design (canonical type normalization).
mapCanonical (frontend/canon-map.ts) maps a canonical node to IR from its
structural form: primitives, literals, units, the top types, arrays, flat
unions and plain records. Nominal objects, callables, index-signature
shapes, tuples, bigint and symbol keep their per-type recognitions through
mapType on the node's checker type.
Under SCRIPTC_CANON_SHADOW=1, every successful top-level mapType also runs
canon and mapCanonical in throwaway registries and appends a JSON line per
comparison (equal, diverge, canon-fail) to SCRIPTC_CANON_SHADOW_OUT. The
census decides which rule families can later move to the normalizer. The
hook is absent otherwise, so no output changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runtime units outside the object core (scr_json.c, scr_js_object.c) read and
adjusted SCR_DYN_OBJ entries in place: v.obj.len, v.obj.entries[i] and the
48-byte ScrDynEntry fields. The single object model's generic objects
(stage S3) change that storage, so these units now go through own-property
accessors in scr_dyn.h:
- scr_dyn_obj_count, scr_dyn_obj_key_at, scr_dyn_obj_value_at and
scr_dyn_obj_prop_at (a ScrDynProp view: key, value, getter, setter and
attributes) walk the properties in creation order;
- scr_dyn_obj_index_of finds one by key;
- scr_dyn_obj_set_attrs_at restricts a member a builtin object just added;
- scr_dyn_own_symbol_prop and scr_dyn_copy_prop_at wrap the symbol-entry
and copy primitives.
The accessors are inline over today's entries, so behavior is unchanged.
21 units move (inspect, assert, util compare, console, http, http2, fetch,
tls, fs, os, lib, array, message, publish, region, symbol, worker events,
handle properties, builtin values, declaration adoption). A package test
keeps entry storage reads inside the core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pushed-down slot's dynamic operations act on its failing components,
whose checker types are not degraded themselves. The lenient mapping
records those components with the declared type they belong to; SC6101
names them ('hook' is a dynamic piece of 'Plugin[]'), and the operation
counts go to the owning slot. Causes read a binding's declared type, not
its narrowed flow type. The coverage test uses its own fixture, so it does
not depend on which interface members main can map.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A re-export from an island package that tsconfig `paths` maps to its
installed directory pushed the package's declaration file into the module
order, so the lowering compiled the declarations' own imports (SC2013 in
the package's .d.mts). Such a re-export now adds no module edge; the
lowering reports the island boundary as for any island re-export.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A monorepo can pin one copy of a package with a tsconfig `paths` entry
that names its installed directory. Its import then resolves as a project
import to the package's declarations, so when the package runs in a Node
island, preflight refused it as types-only (SC1010) and the lowering
skipped it as a project module, leaving its bindings unlowered (SC2013).
Both now treat it as the island import it is: typed by its declarations,
loaded in Node.
Corpus 6839 imports an ESM island package through such a mapping and uses
its interfaces and generic aliases from typed code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An async generator whose body suspends only at its own awaits and yields
now compiles to an LLVM switched coroutine, as async functions of that
kind already do, instead of running on a fiber with its own stack. Its
steps run on the stack that resumes it (the loop's after an await), its
awaits suspend through scr_coro_await_prepare and scr_coro_hop_prepare,
and its yields store OUT through scr_coro_yield_* before suspending.
- scr_async_gen_new_stackless creates the generator. The stackless hop
settles an async generator's state as the fiber hop does, and a step
that would start below the stack guard throws a RangeError, which
rejects its request.
- Calling an async generator's next, return or throw no longer counts as
parking the caller: they queue a request and run the body to its next
suspension, which returns to the caller. Bodies that consume async
generators can now be stackless too.
- An untyped await (async.awaitDyn) has a native coroutine form,
extracted by scr_coro_await_dyn_value, so async bodies that await
untyped values can be stackless.
- An async generator's return through a finally region takes its await
turn through the coroutine when the body is one.
Debug builds, libraries, worker programs and Windows executables keep
fibers, as for stackless async functions.
A const that kept its initializer's layout records the initializer's
checker type by declaration symbol. A function body lowers once per
instance, and only an instance for a wider argument keeps the layout, so
the record from that instance made the function's own lowering bind the
wider layout for `grow(alias)`: the argument converted into a copy and the
callee's writes did not reach the caller. The recorded type now applies
only while it maps to the layout of the binding the current lowering
resolves.
- A const that kept its initializer's layout (`const view: Counted[] = entries`)
is typed by its annotation, so a call that passed it compared the annotated
layout with the parameter's and copied it. The const now records its
initializer's type, and a call that passes it binds that type, at function
and module scope.
- Comparing two recursive layouts (`{ kids: Tree[]; note? }` against
`{ kids: Plain[] }`) recursed without end and stopped compilation with
"Maximum call stack size exceeded". A pair already being compared now holds
for the fields that reach it again.
- The divergence scan reported SC6001 for calls and consts whose layout the
lowering kept, which the coverage test refuses for corpus programs. The
lowering records those arguments and initializers, and the scan skips them.
The native sanitizer build chose the variants to compile by artifact path.
Since the runtime pack stores identical objects once, two variants of a
unit can share a path, so it compiled such a unit for both variants and
linked it twice (duplicate symbols in the sanitized native bootstrap).
selectedRuntimeUnits now returns each selected unit with its variant;
selectRuntimePackArtifacts and the sanitizer both use it.
Staging the native CLI's assets ran llvm-strip unconditionally and failed
on hosts without it. Without llvm-strip on PATH, tsc-ts now keeps its
symbols and the build prints a warning; the release hosts install LLVM.
Node passes a stream's one onwrite function to every _write and _writev
call, and a new function to every _transform call. A write or writev
option callback now receives the same closure on every call
(scr_stream_write_closure), kept or not, so a Set, WeakSet or WeakMap
finds it again. A reused transform, flush, final, construct or destroy
callback is first forgotten by weak collections, as at its free, so a
WeakSet or WeakMap that recorded the previous call's callback does not
find it on the next call.