Commit Graph
278 Commits
Author SHA1 Message Date
Chris Tate e56371e6cc Test scaffolding follows the new fixtures
- assert-fences drops its non-literal expected-object pin (that form lowers now through the errValue key walk — corpus 2556 pins the behavior); the diagnostics snapshot re-records.
- order-parity baselines re-record for the five new corpus fixtures.
2026-07-23 23:05:02 -05:00
Chris Tate b06540dce6 The max-listeners argument ladders answer Node's exact errors
- Instance setMaxListeners over a non-number routes through the checked-dynamic ladder (emitter.setMaxChk): ERR_INVALID_ARG_TYPE with the rendered Received tail, the existing ERR_OUT_OF_RANGE range gate for numbers, and a well-typed dyn still applies.
- events.defaultMaxListeners is a real module property now: reads answer the process-wide default, writes (statement and expression position) validate through emitter.setDefaultMaxChk with Node's own message slot ("defaultMaxListeners"); the qualified static EventEmitter.setMaxListeners(n) shares the ladder under its "setMaxListeners" slot, and the per-target static form with a provably-non-emitter target throws ERR_INVALID_ARG_TYPE on "eventTargets".
- error.argTypeThrow joins error.nodeThrow as the generic always-throwing lowered arm whose Received tail renders at runtime.
- Corpus 2560 pins the ladders byte-identically on both backends.
2026-07-23 22:55:52 -05:00
Chris Tate a6f43649c5 URL.revokeObjectURL's arity contract and fs._toUnixTimestamp
- URL.revokeObjectURL() with no argument throws Node's ERR_MISSING_ARGS ('The "url" argument must be specified') — the check runs before any registry lookup, so the zero-argument contract is exact without blob machinery; the one-argument form and createObjectURL keep their fences.
- fs._toUnixTimestamp lowers over a DOM time value: numeric strings pass ToNumber's loose-equality gate, finite numbers pass (negatives answer now/1000, Node's shape), and everything else throws Node's exact ERR_INVALID_ARG_TYPE with the rendered Received tail.
- Corpus 2559 pins both ladders byte-identically on both backends.
2026-07-23 22:42:55 -05:00
Chris Tate 82cc41ab0a Checkpoint: dyn-handle compat members and the http Agent surface
- ServerResponse/IncomingMessage/Socket members that fenced on checked-dynamic values now dispatch: flushHeaders, getHeaders, setTimeout, cork/uncork with a real writableCorked counter and coalescing flush, writeHead's raw-array form, write/end encodings and deferred callbacks, req.pause/resume with buffered drain, destroyed/readable/bytesWritten/res.req reads, setNoDelay, destroySoon, end(cb), and dyn Buffer at()/slice()
- new http.Agent/https.Agent lowers to a checked-dynamic handle: literal option parsing, getName, destroy, sockets/requests/freeSockets snapshots, settable defaultPort, and REAL maxSockets accounting (over-limit requests defer their dial and queue); keepAlive: true throws the named pooling fence
- the request path threads the agent (http.requestAgent/https.requestAgent rows, both backends); dyn keys now stringify for indexing and the in-operator over dyn receivers (dyn.hasKey)
- differential fixtures 2573-2577 pin both families against Node
2026-07-23 22:40:41 -05:00
Chris Tate ccb4cfa1ae The emittedReadable flag follows Node's emitReadable_/read() lifecycle
- emitReadable_ clears emittedReadable AFTER the 'readable' emit and only when it really fired — a listener observes true during the pass, exactly Node's readable.js.
- read() clears the flag for absent sizes too (Node's `if (n !== 0)` sees NaN there); only read(0) leaves it standing.
- Corpus 2558 pins the lifecycle byte-identically on both backends. The suite's needReadable/resumeScheduled probes remain blocked by the documented stream-tick station divergence (stream ticks drain at the next checkpoint instead of interleaving the user nextTick queue), not by flag semantics.
2026-07-23 22:34:03 -05:00
Chris Tate c82a67f823 Destructure strings through their iterator and stdlib globals as member identity tokens
- Array patterns over checker-typed string sources split once through %str.chars (code points, astral characters whole) and ride the array machinery — declarations, assignments, for-of heads in both declaration and expression position, and parameters; object patterns bind the wrapper's one own data property, length, with renames and dead defaults. Static builds claim exactly what staticStringPattern vets; every other string form keeps the engine gate, and --dynamic keeps the engine route wholesale.
- Object patterns over stdlib globals in JavaScript files bind MEMBER identity tokens (const { subtle } = globalThis.crypto, const { Console } = console — 27 of the node suite's 28 SC1031 first-blockers), so programs run past the destructure and each use meets its own per-site fence; globalThis members are provenance-vetted, bind the member global's own token, and register the stdlibGlobalAliasDecl twin, so surfaced members (Math) keep their receiver-keyed lowerings.
- Members of SURFACED globals (Math.max, process.env, console.log) fence eagerly by member and global name instead of degrading detached uses to quiet token reads, and IR-string sources the checker does not type as strings fence naming the checker's type, never the token's carrier.
- Corpus 2556-2558 pin the string forms and the token identity/truthiness/alias agreements under the Node oracle, byte-identical through both backends; errors.test.ts pins the surfaced-global fence and the lazy per-site token failure.
- order-parity baselines re-recorded additively: the new entries plus the 2510-2555 fixtures that landed without one.
2026-07-23 22:30:58 -05:00
Chris Tate 4656cab090 Record order-parity baselines for the renumbered and new fixtures 2026-07-23 22:26:52 -05:00
Chris Tate 528ef595d4 URLSearchParams' WHATWG argument ladders answer Node's exact errors
- Method values through Function.prototype.call/apply with a receiver that is provably not a URLSearchParams throw ERR_INVALID_THIS ('Value of "this" must be of type URLSearchParams') — the brand check runs before any conversion; searchParams-typed and dyn receivers keep their existing paths.
- Too few arguments throw ERR_MISSING_ARGS with Node's per-method messages before any conversion (the WHATWG arity ladder).
- Name/value slots convert with ToPrimitive's string hint: statically-string args lower directly, symbols throw V8's 'Cannot convert a Symbol value to a string' TypeError, and every other value coerces through the DOM — a user toString/valueOf member is CALLED and its throw propagates (scr_dyn_string_coerce_js, the new dyn.toStringCoerce libCall); func-carrying records box just their toString member into a DOM carrier.
- error.nodeThrow is the new compiler-resolved Node-parity throw for always-throwing lowered arms (the global.undefRead polymorphic-result pattern), shared by these ladders and future ones.
- Corpus 2557 pins the whole surface byte-identically on both backends, including the stringifier's post-sort re-serialization.
2026-07-23 22:23:26 -05:00
Chris Tate fc1f7c212f The C lane compiles without strict aliasing: the punned object header is one effective type
- Upcasts reinterpret derived pointers as base structs, so one object's rc/vt header is legally accessed through two struct types only without TBAA
- clang -O2 elided refcount updates under the aliasing assumption, freeing objects still owned through a base-typed view; the LLVM lane emits no TBAA and was immune
- -fno-strict-aliasing lands on all three compile paths, the standard posture for refcounted-header C object systems
- The regression fixture SIGSEGVs without the flag
2026-07-23 22:22:23 -05:00
Chris Tate ad834307b1 Spread arguments land outside typed rest slots; LLVM promise.reject takes dyn reasons
- Spread calls build one fresh argument array with JS's left-to-right evaluation, on both backends and under --dynamic, with V8's exact non-iterable TypeError texts
- Fence lambdas inside rest arrows spell the hidden rest slot, curing an ICE the destructuring promotions exposed
- The LLVM promise.reject lowering admits dyn reasons, mirroring the C backend and the IR contract
2026-07-23 22:21:53 -05:00
Chris Tate f8b36d9f74 stream/promises compiles statically; out-of-scope modules refuse with named reasons
- pipeline and finished settle real promises over the existing watcher machinery, on both backends
- domain, v8, inspector, node:sqlite, and the underscore-prefixed internals each name why a compiled binary cannot provide them
- stream/web, the Console class, and dns/promises stay honest not-yets with documented paths
- The SC1010 hint gains stream/promises and the long-missing querystring
2026-07-23 22:21:31 -05:00
Chris Tate fa847e2105 DataView setters, fresh-ArrayBuffer erasure, Object.is, and the en-US Intl slice
- The DataView setter family lands end-to-end; fresh ArrayBuffers erase into zero-filled typed-array views; resizable/shared surfaces fence by name
- Object.is compiles with the spec's SameValue across every kind, including union operands
- Composed en-US Intl.NumberFormat and toLocaleString print byte-exact through the Ryu digit core's shortest-round-trip rounding, 30k-double fuzz-verified; the rest of Intl fences by name with reasons
- DurationFormat deliberately fences: Node throws V8-internal Temporal errors with embedded source paths on plain inputs

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-23 22:20:44 -05:00
Chris Tate d7aee7e36d Record width composes with hybrid shapes; index signatures carry functions, Maps, and Sets
- Hybrid records width-coerce in both directions: declared fields copy off struct slots, overflow drops or carries per the target shape
- Per-field lifts cover unknown destinations, derived-to-base upcasts, and clean function adapters, composing recursively
- The overflow store admits func, Map, and Set values; the keyed-miss trap can now describe them (a C-backend ICE fixed)
- Every remaining width and index-signature fence names its first blocking rule
2026-07-23 22:20:11 -05:00
Chris Tate 5d5220b7dc Buffer's invalid-argument ladders answer Node's exact errors
- buf.compare / buf.equals / Buffer.compare arguments that are not statically bytes route through checked-dynamic validators (buffer.compareChk / bytes.equalsChk / bytes.compareChk) running Node's own ladders: ERR_INVALID_ARG_TYPE with the API's argument names and determineSpecificType renders, validateOffset's ERR_OUT_OF_RANGE, undefined slots taking Node defaults — and well-typed dyn values still compute real answers.
- The bytes range ladders (validateOffset, boundsError, checkInt, swap, fill, randomBytes, fs.readSync) stamp their Node codes (ERR_OUT_OF_RANGE / ERR_BUFFER_OUT_OF_BOUNDS / ERR_INVALID_BUFFER_SIZE / ERR_INVALID_ARG_VALUE); validateOffset renders ±Infinity as 'an integer' like Number.isInteger.
- The deprecated `new Buffer(number, enc)` string arm throws Node's ERR_INVALID_ARG_TYPE (the throwing path fires no DEP0005, so compiled silence matches); other ctor forms keep their fences.
- assert.throws/rejects accepts a plain expected object held in a variable (the shared `{ code: ... }` expectation record) through the errValue key walk.
- scr_dyn_specific_type/scr_dyn_arg_type_fail move beside the DOM core (scr_json.c) so always-linked validators render through them; the handle unit keeps the listener gate.
- The differential lanes learn `// @no-deprecation` (Node oracle runs --no-deprecation) for fixtures pinning deprecated-API error paths; corpus 2556 pins the whole ladder byte-identically on both backends.
2026-07-23 22:06:53 -05:00
Chris Tate 07961782ef node:stream/promises compiles statically: finished and pipeline settle void promises
- The promise forms ride the callback machinery: scr_sp_finished/scr_sp_pipeline park a settling watcher (fulfilled on a clean finish, rejected with the stream's error or ERR_STREAM_PREMATURE_CLOSE), verified byte-exact against Node including the already-closed async settlement
- lower-stream's stream/promises arm replaces its not-yet fence: finished(stream) and pipeline(...streams) lower to sp.finished/sp.pipeline (stream stages only — the options argument and iterable/generator/function stages keep pointed fences); both specifier spellings and the CJS require forms resolve
- Both backends emit it: the C compound-literal stream list and an LLVM stack array beside the callback pipeline's emission; sp.* joins the may-throw seeds, the loop-liveness set, and moduleUsesStream's link gate
- The SC1010 hint gains stream/promises and the long-missing querystring; hint snapshots and the surface manifest repin
- Corpus 2560/2561/2562 pin pipeline success/error teardown, finished's clean/errored/premature/writable arms, and the CJS destructured-require spelling — green on both backends and under ASan + RC audit
2026-07-23 22:05:28 -05:00
Chris Tate 4749a9f5cb Rest-spread forwarding compiles through a runtime-arity lane instead of ICEing
- `const f = (...args) => from(...args)` ICEd with SC9001: the fenced arrow's PoisonError deferral built the runtime-fence lambda from funcType.params, which excludes the hidden trailing DOM-array param a rest-marked type implies — the closure no longer matched its own target signature. The fence lambda now spells that slot (island rest types already spell theirs), so any future fence inside a rest arrow stays a clean deferral.
- The idiom itself now compiles: spread arguments a compile-time completion cannot take ride a new runtime-arity lane (lowerSpreadArgsCall). Checked-dynamic spreads box the callee (dynFrom) and emit a spread-marked dynCall — both backends build one fresh DOM argument array (plain args move in, spreads flatten via the new scr_dyn_arr_push_spread) and apply through scr_dyn_apply, the boxed thunk delivering JS arity; island (--dynamic) spreads marshal the callee and ride the new jsOp callSpread through an island prelude helper with real `f(...pre, ...s)` spread syntax.
- Non-iterable spread sources throw V8's exact spread-call TypeError texts, catchably, on both lanes: nullish sources spell the spread expression ("v is not iterable (cannot read property undefined)"), everything else the generic Spread-syntax text.
- Direct calls of the --dynamic islandRest lambdas complete like completeArgs' island pack (fixed slots positionally, surplus into one fresh engine array), so `f(1, 2)` on a `(...args) =>` binding works at any arity.
- Corpus fixtures 2557-rest-spread-forward.js and 2558-rest-spread-forward-dynamic.js: forwarding to a declaration, a closure, a const re-binding, leading fixed params, rest-to-rest chains, mixed spread positions, JS-arity drops/fills, string/array spread sources, and the error texts — byte-identical vs Node on both backends and under --dynamic.
2026-07-23 21:52:42 -05:00
Chris Tate 232add4a3f Compile the emitted C with -fno-strict-aliasing: hierarchy upcasts type-pun the object header
- A hierarchy upcast in the C backend is a raw pointer cast, so one object's rc/vt and fields are accessed through both the base and derived struct types; clang's TBAA at -O2 treated those as non-aliasing and reordered/elided the inlined rc updates, freeing an upcast-compared object while a global still owned it (SIGSEGV in sc_release_globals — a runtime miscompile, not a compile-time crash; the generated C's RC bookkeeping was already balanced).
- The flag joins all three lockstep option sets: compileC's single-invocation buildArgs, the cache-miss cflags, and compileLibArchive; the cache key hashes the full command line, so stale binaries and runtime objects re-key on their own.
- The LLVM backend emits no TBAA metadata, so the flag also makes the two backends' memory semantics identical instead of coincidentally so.
- New differential fixture 2556-upcast-identity.ts: identity compares through a three-deep hierarchy at every view, both operand orders, !==, constructing operands on either and both sides, compares as call arguments and as standalone expression statements, and field liveness after the compares — it SIGSEGVs without the flag and is Node-exact with it on both backends, plain and sanitized.
2026-07-23 21:35:58 -05:00
Chris Tate 98f4d79353 Record order-parity baselines for the toesm and dyn-array fixtures 2026-07-23 21:30:11 -05:00
Chris Tate 3cc6121aca LLVM promise.reject takes the checked-dynamic reason the C backend always had
- Promise.reject(value) with an untyped JS reason (a dyn-typed arg — the tracingChannel promise shape) crashed the LLVM emitter's object-only assertion; the IR contract (validate.ts) admits object OR dyn reasons and the C backend already emitted both.
- Widen the assertion and route the dyn reason through emitThrowValue's existing REF arm — scr_throw_ref with the dyn retain/release pair and no trace, byte-identical to the C lowering; identity flows to catch/unhandledRejection observers.
- Corpus fixture 2556-promise-reject-dyn-reason.js: Error/string/number/object reasons through .catch and awaited rejection, byte-identical vs Node on both backends.
2026-07-23 21:29:49 -05:00
Chris Tate 6328f47bfd Out-of-scope builtin modules say why: v8, inspector, node:sqlite, domain, and the underscore internals
- SC1010's wording routes through one helper (unsupportedModuleFeatureOf): pending modules keep the plain not-yet story, out-of-scope ones append the reason
- v8/inspector name the missing V8 engine, node:sqlite the unbundled SQLite library, domain its deprecation and engine-level interception, and the fourteen underscore-prefixed legacy modules their Node-internal implementation objects
- All five fence sites reword together: the four import/require edges in program.ts and the fenced-binding use site in lower-exprs.ts
- New diagnostics fixture module-out-of-scope.ts pins the reasons (and that stream/web stays plain not-yet); the coverage import-fences snapshot repins with the v8 reason
2026-07-23 21:27:19 -05:00
Chris Tate a21a7dd035 Library mode takes the static-or-refuse posture for bare npm specifiers
- Eligible packages compile statically into the archive through the npm-static machinery, transitive deps judged to the same bar at a fixpoint
- Ineligible packages refuse with a teaching naming the package, the failed bar, and the vendor-or-drop remedy — never the executable lane's dynamic advice
- npm module bytes join source_hash and build_id; npm declarations stay out of the contract type tables by construction
- Inter-package requires run the dependency's init in both lanes

# Conflicts:
#	packages/compiler/src/diagnostics/diagnostic.ts
#	packages/compiler/src/frontend/lowering/lowerer.ts
#	packages/compiler/src/index.ts
#	tests/harness/library-mode.test.ts
2026-07-23 21:07:29 -05:00
Chris Tate b49aea5996 esbuild's __toESM external-dependency interop compiles statically under --npm-static
- The helper pair verifies by structure; recognized call sites pad down to the bare require at original offsets so the dead helper chain sweeps away
- .default semantics decided at build time per Node: binding for node-mode and unstamped CJS targets, member read for __esModule-stamped ones
- Bare requires of opted-in packages now run the dependency's init and bind as CJS namespaces — a static-claimed package can no longer crash at load
- Anything recognition can't finish degrades the package to the island with the construct named; auto mode shares the path
2026-07-23 21:04:14 -05:00
Chris Tate 8752ed3117 Record order-parity baselines for the new stdlib fixtures
- Adds the ts7 canary entries for 2560-2562 and the intl diagnostics fixture, plus the missing baselines for the pre-existing 2510-2513 and 2520-2523 fixtures the re-record picked up — purely additive, nothing removed or changed.
2026-07-23 20:52:10 -05:00
Chris Tate 8fd43a50c8 Record order-parity baselines for the new fixtures
- purely additive re-record: the four width/index-signature corpus programs and the record-width diagnostics fixture, plus eight earlier corpus entries (2510-2513, 2520-2523) the default canary subset had never sampled
- no existing entry's order or preflight diagnostics changed
2026-07-23 20:36:59 -05:00
Chris Tate a0d2ce24f5 Record width composes with hybrid shapes, and per-field lifts cover unknown, upcasts, and clean function adapters
- recordWidthPlan narrows index-signature sources into declared-only targets (declared fields copy off their struct slots, the overflow drops with the width); absent-field completion declines there — the overflow could hold the key at runtime, and a completed undefined would drop the value Node keeps
- the overflow capture initializes same-named declared target fields directly off the source, admitting REQUIRED declared members; literal writes to names the target does not declare go overflowOnly, and a dispatch-writes gate keeps the validator's collision rule for the rest
- widthLiftPlan gains dynIn (a typed field into an 'unknown' slot — the static→DOM deep copy), upcast (derived into base, the prefix-layout reinterpret), and funcAdapt (funcCoerceAdapter's CLEAN subset — the stranded trap-only dispositions stay top-level, never inside a width plan)
- SC2002 now names the first blocking rule per record pair (describeRecordWidthBlocker mirrors the planners' gates) and its message/hint/registry entry speak the width-copy truth — the manifest flips the one entry name
- corpus 2556/2557 pin both hybrid directions and the new lifts byte-identical vs Node on both backends; diagnostics fixture record-width pins the residue's pointed messages
2026-07-23 20:36:50 -05:00
Chris Tate 176d963ea1 Index signatures carry function, Map, and Set values through the overflow store
- isSupportedIndexValue admits func/map/set: the overflow map's RC and trace machinery is already fully type-directed (scr_closure/scr_map adapters, the cycle fixpoint's map rule recursing on the value), and nested index-signature records rode the record kind all along
- the C keyed-read miss trap can now describe Map/Set-typed slots (dynDesc), reachable only through that message — no dynCheck ever expects a container
- SC2006's message and hint name the real value domain (functions, Maps, Sets, RegExps, Promises, ...) and the honest blockers left: symbol keys, dual signatures with unequal value types, and values with no representation at all
- diagnostics fixtures pivot to still-fenced shapes (Uint8Array values, unequal duals, a Date-valued bag); corpus 2558/2559 pin the command-registry and container-table patterns — declared+overflow hybrids, enumeration, delete, registry-capturing cycles, container churn — byte-identical vs Node on both backends
2026-07-23 20:36:21 -05:00
Chris Tate fa7cc089a9 The en-US Intl slice: composed NumberFormat and toLocaleString go static, the rest fences by name
- new Intl.NumberFormat("en-US").format(x), the callable spelling, and x.toLocaleString("en-US") lower to one runtime formatter with default options: decimal notation, 0-3 fraction digits rounded half-up on the SHORTEST round-tripping decimal (ICU's rounding input, probed against Node — format(1.0005) is "1.001" though toFixed(3) answers "1.000"; 1e23 prints the shortest form's zeros, not the exact expansion), "," grouping, "∞"/"NaN" texts, and "-0" for negatives rounding to zero. The en-US/latn symbols are the whole embedded locale surface — no ICU is linked.
- The Ryū digit core factors out of scr_f64_to_str as scr_f64_digits (same bytes; the 1M-double oracle gate stays green) and feeds the formatter; a 30,000-double ad-hoc fuzz across magnitude regimes matched Node byte-for-byte on both backends.
- Every unlowered Intl form fences by NAME with its own reason: the environment-default locale a compiled binary cannot carry (no-locale forms), other locales and the rest of the namespace (DurationFormat, DateTimeFormat, PluralRules, getCanonicalLocales — ICU data the binary does not embed; Node 24's DurationFormat additionally throws V8-internal Temporal errors with embedded source paths, so byte parity there is a version-dependent landmine), options bags, formatter values (the composed form is the lowered shape), and non-number format arguments.
- Differential fixture 2562 pins the rounding discriminators, grouping, ±0/NaN/∞, tiny and huge edges, and both spellings byte-identically vs Node on both backends; the new intl diagnostics fixture pins every named fence, and the Intl hints across the identifier/member/type chokepoints now teach what lowers.
2026-07-23 20:30:47 -05:00
Chris Tate 6162354076 esbuild's __toESM external-dependency interop compiles under --npm-static
- The bundler-CJS rewrite erases recognized __toESM(require("x")) wrappers — module-scope bindings, the `, 1` node-mode variant, inline member reads — down to the bare require at its original offsets; .default accesses pad to the binding where Node's answer is the module (plain-CJS targets, node mode) and stay member reads for __esModule-stamped targets, decided by a require-resolution probe over the target's shipped source (forwarding chains followed)
- Interop the recognizer cannot finish — deviant helper text, escaping interop objects, non-literal arguments, ESM-output calls — answers { degrade }: the fs shadow reports the package as an offender with a note naming the construct and the fallback loop islands it, never a failed build and never the silent alternative (a live helper chain's `var __create = Object.create;` fences at module load while the report claims static); auto and explicit opt-ins share the path
- Bare requires of opted-in packages now behave like relative ones end to end: requireInitStmt emits the dep's inline %init (previously the module never initialized before its exports were read), and cjsLocalModuleBindingOf/resolveValueSymbol resolve member reads and namespace method calls through the npm-static dep's export table
- Erased wrappers join the neutralize set so the helper sweep drops the whole dead chain, hoisted canonical-table texts copied off the source drop .default on module-valued bindings exactly like the pads do, and a file with interop pads but no canonical table still rewrites pads-only
- Fixtures gtcore/gtwrap/gtdrift with cases 2556-toesm-external (byte-matches Node across both dep shapes and the comma-call spelling) and 2557-toesm-drift (construct-naming degrade under explicit and auto opt-ins), excluded from the flagless island lane like the 246x family
2026-07-23 20:23:42 -05:00
Chris Tate b31d160fbd Object.is goes static with the spec's SameValue
- Number pairs take a new scr_num_same_value runtime primitive (NaN equals NaN, +0 differs from -0 — the two divergences from ===); strings, booleans, unit literals, and every reference kind (arrays, records, class instances with hierarchy widening, functions, class values, Maps/Sets, symbols, typed arrays, promises) ride exactly the strict-equality machinery, whose answers SameValue shares.
- Union operands lower through unionEq, which grows a sameValue flag: the f64 arm's payload compare upgrades to SameValue (a separate interned C helper per union; the LLVM lane calls the runtime primitive inline), every other arm is shared between the two semantics.
- Statically disjoint kind pairs answer the constant false with the operands still evaluated for their effects (Object.is is (any, any) — tsc admits every pair); dyn/jsval operands and partially-overlapping unions fence by name with strict equality's validate/narrow-first stance.
- Differential fixture 2561 pins the NaN/±0 edges, mixed-kind calls with effect counting, reference identity across kinds, and union payloads (including NaN/±0 inside a union and union-vs-plain wraps) byte-identically vs Node on both backends; stdlib-fence pins the named fences.
- Found pre-existing (reproduced at the base commit): the C backend SIGSEGVs on console.log(asBase === d, asBase === new Base()) — an upcast identity compare plus a constructing compare in one call; the fixture avoids the shape and the bug is reported for a follow-up lane.
2026-07-23 20:15:31 -05:00
Chris Tate f9cbff164f Library mode takes the static-or-refuse npm posture: eligible packages compile, SC4013 refuses the rest
- compileLibrary runs the --npm-static eligibility bar automatically over every bare specifier the graph exposes (opted-in packages' own import and top-level require edges included, iterated to a fixpoint) — an eligible package compiles statically into the library graph, and every fallback the shared frontend records refuses as SC4013 naming the package, the specific failed bar, and the vendor-or-drop remedy (with the profile's SC4013 teaching riding the hint), never SC1010/SC2013's executable-lane teachings.
- requireInitStmt now emits the guarded %init call for a bare require naming another opted-in package: the dep's module body never ran before (NULL-closure crash at first use), a latent npm-static gap on both lanes that the library fixpoint hits immediately; the executable lane never exercised an inter-package require.
- Contract facts exclude statically-compiled npm files (their .d.ts is dropped by construction, so no npm declaration can name a wire-contract type) while their bytes keep joining source_hash/build_id through moduleOrder; the sidecar stays byte-deterministic and a package-file edit flips both identity hashes.
- Builtins keep their story under the npm posture: async_free builtin imports compile alongside npm code, event-loop surfaces still refuse SC4005, no new fence.
- New fixtures npm-static, npm-refuse, and contract-npm vendor fake packages per the tests/fixtures/npm precedent; the K12 suites assert the compile and refusal sides on both emissions and the contract suite covers the identity-hash implications.
2026-07-23 20:06:31 -05:00
Chris Tate e1f93475cb Deliver runtime-detected library traps structured, with codes, entry symbols, and profile overlays
- The library trap funnel (scr_library.c) now assembles every detected trap into the ratified 0x01 text 0x1F code 0x1F symbol [0x1F remediation] form: the baseline human line rides unchanged as field 0, and 0x01-led messages (facade-authored throws, the compile-time SC4012 contract trap) still pass verbatim.
- Trap kinds classify over the runtime's own message conventions into a new SC4013-SC4019 runtime code family (escaped exception, range, type, syntax, OOM, internal invariant, residual), documented beside SC4012 in the diagnostics registry; no arithmetic kind exists because no runtime site can fire one.
- Every generated entry prologue (init, reset, collect, each export, both emissions) records its external symbol in a SCR_LIB-only static slot the funnel reads, so the structured message names the trapping entry as the host linked it; profile teachings/remediations for the family land as a program-TU overlay table both backends emit identically.
- K5/K7/init-trap fixtures now pin the structured layout with the spec's parse rule (default text, code, entry symbol, fields=3 on a no-teachings profile), and new K12 tests pin the SC4014 teaching/remediation overlay plus the undeclared-code default; the verbatim and text-leads-printable pins stay.
- The default executable lane is byte-identical (signature-stripped cmp over a stash/rebuild pair); all library suites and the diagnostics suite are green on both emissions.
2026-07-23 19:57:38 -05:00
Chris Tate 9a7e42fd1b Fresh-ArrayBuffer erasure, DataView setters, and named buffer fences
- new Uint8Array/Uint32Array/Int32Array/Float32Array(new ArrayBuffer(n)) erases the buffer into a zero-filled view exactly like the SharedArrayBuffer sleep idiom (literal byte length divisible by the element size), and new DataView(new ArrayBuffer(n), off?, len?) views a fresh zeroed allocation — nothing else can reference the buffer, so the erasure is exact.
- The DataView setter family (setUint8/Int8/Uint16/Int16/Uint32/Int32/Float32/Float64) lowers end to end: one scr_dataview_set runtime mirror of the getter (ToIndex offsets, Node's constant RangeError, modular integer truncation, double→float rounding, both endiannesses) wired through both backends; the bigint and Float16 setters keep the member fence.
- The resizable surface fences by NAME everywhere it can appear: new ArrayBuffer/resize/transfer/maxByteLength and SharedArrayBuffer.grow now state the reason (no free-standing ArrayBuffer value exists; grow has no threads to share with) at the constructor, identifier, member, and declared-type chokepoints, including the maxByteLength options bag inside an otherwise-erasable position.
- Differential fixture 2560 pins the erasure forms, every setter width/endianness/coercion, aliasing through x.buffer views, and the RangeError edges byte-identically vs Node on both backends; es2025-fences/dataview/stdlib-fence snapshots pin the new named hints.
2026-07-23 19:45:15 -05:00
Chris Tate 33178e791d Release 0.0.8 2026-07-23 19:08:22 -05:00
Chris Tate 7b8e1452b3 The monomorphization frontier: keyof constraints, interface receivers, and signature bindings
- keyof-constrained generics specialize per literal key; keyed record writes take the literal-key type rule
- Generic methods called through interface-typed receivers monomorphize against the proven class
- Generic-signature annotations monomorphize their function initializers; generic-fn aliases register their target
- Fixtures 2550-2555 byte-identical on both backends

# Conflicts:
#	packages/compiler/src/diagnostics/diagnostic.ts
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	packages/compiler/src/frontend/lowering/lower-stmts.ts
#	packages/compiler/src/frontend/types.ts
#	packages/compiler/test/ts7/baselines/order-parity.json
#	tests/harness/__snapshots__/generic-methods.ts.txt
#	tests/harness/__snapshots__/generic-value-bindings.ts.txt
#	tests/harness/__snapshots__/mixins.ts.txt
#	tests/harness/__snapshots__/stdlib.ts.txt
2026-07-23 18:48:49 -05:00
Chris Tate 18f2cf2df0 Library traps carry the structured teaching encoding
- Trap messages assemble as 0x01 text 0x1F code 0x1F symbol with an optional remediation field, built once at export resolution so both backends emit identical bytes
- Thrown messages already leading with the marker pass to the sink verbatim through a length-delimited funnel entry
- Profiles refuse teaching and remediation strings containing the reserved bytes; embedder codes validate only as clean tokens
- A conformance fixture parses the structured bytes with the ten-line host rule and pins printable-first-byte on baseline messages

# Conflicts:
#	packages/compiler/src/diagnostics/diagnostic.ts
2026-07-23 18:01:24 -05:00
Chris Tate d6b57cf2be Sidecar tables define-or-refuse on order-ambiguous declarations
- SC4010 refuses tabled types gathered from multiple declaration sites, naming every site; SC4011 refuses conditional and mapped types with a point-at-one-declaration teaching
- Spread-composed unions flatten depth-first in source order; duplicate arms across constituents keep the first occurrence
- Conformance fixtures pin cross-file payload order, a global-shadowing enum member, and alias transparency on both emissions
- Contract facts now collect across the program's source files, and type-only modules join source_hash/build_id
2026-07-23 17:59:14 -05:00
Chris Tate 9d811bf7b5 Pin declaration-order conformance and define-or-refuse for order-ambiguous sidecar inputs
- New contract-order conformance fixture (both emissions): union arms anti-alphabetical with payload records declared in a separate file (import order never perturbs table order), an anti-alphabetical enum with a global-shadowing "Infinity" member, and a union referenced through a type alias (the table derives from the aliased declaration; the alias adds no entry and no reordering).
- Contract facts now scan the whole program graph, not just the entry: type-only modules join (they have no runtime edge, so they ride the program's source files rather than moduleOrder), non-entry modules anchor after the entry in bytewise path order, and the identity hashes cover type-only modules so contract-bearing edits always move source_hash/build_id.
- SC4010: a tabled or designated type whose members gather from multiple declaration sites (interface merging, module augmentation, a same-name exported type in another module) refuses with every site named file:line.
- SC4011: a conditional or mapped type producing a tabled/designated type refuses with its own teaching.
- Union composition (type Msg = A | B where A/B are kind-tagged unions) is allowed and pinned: arms flatten depth-first in source order, each constituent in its own declaration order, and an arm name appearing in several constituents keeps its first occurrence; a repeat among one declaration's own inline arms still refuses.
2026-07-23 17:57:09 -05:00
Chris Tate b2b880e462 Record order-parity baselines for the generics-frontier fixtures
- Baselines added for corpus 2550-2555 (plus the previously unrecorded 2490-2500 entries the sweep now sees); no recorded entry changed.
- tests/diagnostics/generics.ts keeps its alias/annotation sections as reached COMPILING bodies (their absence from the snapshot is the point), fixing the dangling calls the earlier edit left.
2026-07-23 17:51:13 -05:00
Chris Tate f1b0048325 Structured trap teachings: the library sink message grows a versioned encoding
- A library-mode trap that carries a diagnostic code and/or the trapping symbol is assembled as 0x01 text 0x1F code 0x1F symbol [0x1F remediation]: the teaching text leads so plain-text hosts degrade gracefully, the whole remediation field is absent when the profile supplies none, and the frozen sink signature is untouched — all structure lives in the message bytes. The compiler assembles the buffer once at export resolution (library/trap-teaching.ts), both backends emit the same bytes, and the runtime funnel carries them opaque; the first wired site is the inbound-bytes host-contract trap (new runtime code SC4010), naming the trapping export's C symbol.
- The profile's determinism riders now feed the encoding: teachings gains a remediations sibling, both keyed by code, with keys validated only as tokens free of the reserved bytes (SC space stays the compiler registry's; embedder codes are embedder-prefixed) and values refused at load (SC4001) when they contain 0x01 or 0x1F.
- The escaped-exception renderer honors the verbatim rule: a thrown string or Error message that already begins with 0x01 reaches the sink byte-for-byte — no "Uncaught " prefix, no added newline — which is how facade-authored structured teachings ride the throw channel; everything else keeps the baseline shape, whose printable first byte is what makes the marker unambiguous.
- New K11 conformance fixture (tests/library-mode/teach) drives all of it through a real C host on both emissions: exact structured byte layout via the spec's parse rule, verbatim 0x01-led throws (Error and string), and the printable-first-byte pin on baseline trap and throw messages; library-profile tests cover the remediations round-trip and the reserved-byte refusals.
- The executable lane is untouched: every change sits behind SCR_LIB or the library emission path, verified by byte-identical executable binaries on both backends before and after.
2026-07-23 17:45:44 -05:00
Chris Tate e528169a37 Keyed record writes take the literal-key type rule
- `o[k] = v` whose key identifier's type proves one literal — a keyof-bound parameter inside a generic instance, or a literal-typed const — lowers to a static field write, mirroring the read side.
- Literal-UNION keys keep the fence (a runtime-keyed write needs an index signature), with the instantiation context naming the binding.
- Fixture 2555 (writes, read-modify-write, literal-typed key consts, per-literal swaps), byte-identical vs Node on both backends.
2026-07-23 17:41:18 -05:00
Chris Tate ced1822c24 Destructuring assignment targets, class-instance sources, class rest, and accessor defaults go static
- Nested patterns and member targets in assignment position, with JS's reference-before-value evaluation order pinned
- Class-instance sources call getters exactly once per element; a latent double-call in the accessor-default path is fixed
- Class rest packs the inheritance chain in Node's key order, fencing where enumeration can't be reproduced
- Fixtures 2536-2540 for the promotions; 2530-2535 and 2541 pin the already-static declaration-position surface

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-23 17:32:49 -05:00
Chris Tate 385b0a9199 Promote destructuring assignment targets, class-instance sources, class rest, and accessor defaults to the static tier
- Destructuring assignment now takes nested patterns (`({ p: { q } } = src)`, `[[a], b] = t`) and property/element targets (`[c.x, box.vals[i]] = arr`, `({ a: rec.f } = o)`, rest into members) — receivers temp at their pattern position, JS's reference-before-read order; defaults on member targets test against the target's own type. Defaults on nested targets keep a named fence (no single binding type to test against).
- Class instances destructure in assignment position like they already did in declarations: field slots and getter calls per element via the same fieldGetExpr dispatch, defaults, renames, and member targets; methods and undeclared names keep their pointed fences.
- Rest over class-instance sources packs the remaining instance FIELDS into a fresh record (JS's CopyDataProperties copies own enumerable properties — never prototype methods/accessors, matching the checker's rest type), in declarations and assignments; non-public fields, runtime-provided chains (Error/EventEmitter/stream), and key orders the packed shape cannot reproduce fence instead of diverging.
- Defaults on destructured getter results now lower (record and class accessors alike), with the getter result temped so the default's undefined test never calls it twice; numeric-literal pattern keys (`const { 2: x } = o`) fold to JS's canonical property name.
- Corpus 2536-2540 pin the new forms byte-identically against Node on both backends; the destructuring-fences diagnostics program keeps the honest residue (method extraction, non-public rest fields, nested-target defaults, index-signature rest).
- The ts7 order-parity baseline re-record backfills the entries the released file was missing (2490-2500 included) alongside the new fixtures; no existing entry changed.
2026-07-23 17:27:25 -05:00
Chris Tate ce0dc9ee70 Generic-signature annotations monomorphize their function initializers
- `const g: Mapper = (x) => x` under `type Mapper = <T>(x: T) => T` (aliases and inline generic function types alike) registers like `const g = <T>(x: T) => x`: the type parameters come off the annotation's one call signature, whose symbols are exactly what the checker types the initializer's parameters by (bindingContextualGenericFnNodeOf).
- Function-expression initializers, pinned-value uses, aliases of the annotated binding, and multi-parameter signatures with generic callbacks all ride the existing instance table.
- The SC2001 generic-signature message now names every binding shape that monomorphizes; escapes with no statically enumerable instantiations (generic-signature parameters, stored results) keep the fence with that precise wording.
- Fixtures 2553 (alias and inline annotations, function expressions, pinned values) and 2554 (keyof + aliases + signature bindings + interface methods composing), byte-identical vs Node on both backends.
2026-07-23 17:26:56 -05:00
Chris Tate f66a4e0bf2 Generic methods through interface-typed receivers monomorphize against the proven class
- `const r: Repo = new MemRepo()` over an interface whose every member is a generic-callable method keeps the initializer's CLASS representation (genericIfaceBindingKeepsClass): the record shape maps empty — generic members are excluded — so the width copy would drop the class the calls dispatch against, and in JS the binding IS the instance.
- Calls like `r.get(...)` resolve through exactInstanceClassOf to a class generic-method call on the exact class: `this` works, state mutates on the one real instance, and record-typed USES width-coerce at the use site instead of the declaration.
- Receivers with no provable class (parameters, `let`, produced values) keep the fence, now naming the actual blocker: static dispatch needs a const bound to its `new` expression.
- Fixture 2552 (stateful implementations, two classes, block scope, casts back to the class, record-typed uses), byte-identical vs Node on both backends.
2026-07-23 17:23:49 -05:00
Chris Tate 0386d8c00f keyof-constrained generics monomorphize per literal key; generic-fn aliases register their target
- Instantiations carry ts-level bindings alongside the mapped IrTypes: a `K extends keyof T` parameter keys its instance on the bound literal (no cross-literal sharing — the bodies read different fields), and literals forward through generic-to-generic calls.
- `T[K]` inside instance bodies resolves through the bound checker types (mapBoundIndexedAccess via the new resolveTypeParamTs hook), and `o[k]` reads whose key TYPE proves one literal — a keyof-bound parameter or a literal-typed const — lower to static field reads.
- `const h = id` registers the target's GenericFnInfo under the alias's symbol (chains, block scope, pinned values, and calls all resolve like the target's own name); non-const/reassigned aliases fence by name, and unpinned VALUE uses keep their fence at the reference.
- Fixtures 2550 (keyof/pick shapes, forwarding, runtime keys, literal-typed key vars) and 2551 (alias chains, contextual pinning, annotated generic aliases), byte-identical vs Node on both backends.
2026-07-23 17:20:03 -05:00
Chris Tate aa8d31c3cd SC2001 splits into named type-shape fences
- SC2005 generic call signatures, SC2006 index-signature shapes, SC2007 overloads, SC2008 unresolved intersections, SC2009 supported shapes over an unsupported component with the component named
- Stdlib nominal types reroute to the SC2020 story instead of a duplicate code
- SC2001 keeps only bigint/symbol, constructor objects, and unresolved library generics — its registry name now says so
- Five new diagnostics programs pin one frame per code; manifest entries added; zero behavior change to accepted programs

# Conflicts:
#	tests/harness/__snapshots__/regex.ts.txt
2026-07-23 17:17:31 -05:00
Chris Tate e5d4825a77 Private statics resolve through provably-exact class values
- X.#m() calls, const-bound class expressions, decorated names, and const aliases all reach static #private members when the receiver provably holds the declaring class
- The write path's exactClassOfReceiver and leaf-class rules extend to the call and read paths through one shared predicate
- Slots that can hold a descendant still fence; the read path gains the subclass-spelling fence the other paths had
- Fixtures 2520-2523 on both backends
2026-07-23 17:11:48 -05:00
Chris Tate 059d603786 Evolving-any arrays keep their handle-element lowering at method calls
- tsc's evolving-array analysis answers the pushed element type at later call sites; the monomorphizer trusted it while the receiver value lowered as array<jsval>, producing an ill-typed call the validator rejected
- The receiver value's element type now wins across the array method family, riding the explicit-any[] handle path; unannotated locals and file-scope aliases adopt the same spelling
- .find on handle-element arrays fences honestly instead of ICEing
- Fixtures 2510-2513 under --dynamic, byte-identical on both backends
2026-07-23 17:11:30 -05:00
Chris Tate 0685c867e9 Eleven coded stops on the formatter-runtime chain fixed at the compiler
- T[K] indexed access resolves against instantiations; Object.hasOwn/assign on records and DOM values; runtime-keyed writes to fixed shapes with a key-miss trap
- Regex union arms with instanceof RegExp as the tag test; string | RegExp shapes map
- Island literals gain getters, spreads, and real TemplateStringsArray identity for tagged templates
- JS variadics under --dynamic bind the engine's arguments array; fn(...args) lowers to engine apply
- Fixtures 2470-2476 on both backends; SEMANTICS entries 385-390

# Conflicts:
#	packages/compiler/src/frontend/types.ts
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-23 17:11:29 -05:00
Chris Tate 64bcb304e2 Monomorphize evolved-any array methods on the value's handle element under --dynamic
- An evolving-any array (const fns = []; fns.push(() => 1)) lowers array<jsval> at its any[] declaration, but tsc's evolving-array analysis answers the pushed element type at later method calls, so the lowering handed monomorphized helpers a jsval-element receiver under a typed signature — the SC9001 validator ICE.
- lowerArrayMethodCall now adopts the receiver VALUE's element type when it is a jsval-element array, riding the explicit-any[] handle-element lowering; HOF callback lead params bind the handles via jsvalParamOverrides whatever the evolved contextual type spelled.
- Derived bindings keep the handle-element array instead of the evolved spelling: locals adopt the initializer's type in lowerVarDecl, and file-scope globals register the handle-element array for aliases and receiver-type-preserving chains (filter/slice/splice/concat) rooted at a registered one.
- concat over a handle-element receiver decides spread-vs-element by the argument VALUE's array-ness, exactly JS's IsArray, instead of the evolved checker spelling.
- Corpus fixtures 2510-2513 pin map, filter/forEach/some/every/findIndex, mixed push sites, and the slice/concat and function-scope derived shapes, byte-identical vs Node under --dynamic on both backends.
2026-07-23 17:01:32 -05:00