Commit Graph
354 Commits
Author SHA1 Message Date
Chris Tate 0423093a99 Node's validation ladders throw where implemented APIs were fenced
- Buffer compare/equals/constructor ladders, URLSearchParams WHATWG brand checks and arity ladders, URL.revokeObjectURL arity, and the max-listeners family answer Node's exact typed errors
- ToPrimitive string-hint coercion runs user toString/valueOf with throw propagation; assert.throws accepts expected-object variables
- Reusable always-throwing libCall shapes (error.nodeThrow, error.argTypeThrow) seed the remaining validation-ladder work
- Two emittedReadable lifecycle bugs fixed; ERR_* codes stamped across the bytes/crypto/fs range ladders

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-23 23:37:03 -05:00
Chris Tate 53bad7830e Typed-lane socket compat: pause/resume/setNoDelay/destroySoon, finish, bytesWritten/readable, handle identity ===, Agent type maps to dyn
- the static net.Socket lane gains pause()/resume()/setNoDelay() (chaining, like Node), destroySoon(), on/once('finish'), and the bytesWritten/readable reads — the same runtime entry points the dyn dispatcher uses
- strict equality over runtime handle kinds is pointer identity (c.pause() === c — JS object equality; validator and both backends)
- http/https Agent TYPES map to dyn (the value IS the DOM handle), the stdlib member fence steps aside for dyn-mapped receivers, and ClientRequest joins DYN_HANDLE_KINDS so typed client requests box by reference
- fallback declarations catch up (Socket flow control, the full Agent surface incl. getName/defaultPort/protocol); fixture 2575 pins flavor-independent dyn Buffer at()/slice()
2026-07-23 23:33:20 -05:00
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 24ea14a814 Runtime-detected library traps deliver the structured teaching form unconditionally
- SC4013-SC4019 classify the runtime trap kinds (escaped exception, range, type, syntax, OOM, internal invariant, residual); the funnel assembles text/code/symbol with the baseline line unchanged as field 0
- Every generated entry prologue records its exported symbol for the funnel; init counts as an entry
- Profile teachings and remediations overlay per code through a program-TU table identical on both backends
- The executable lane is proven byte-identical; K5/K7/K11 pins extended, K12 pins the overlay and default forms
2026-07-23 21:03:55 -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 v0.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 057ac2cea2 Pin corpus coverage for the already-static destructuring surface
- 2530-2535 and 2541 lock in the destructuring forms the static tier already compiled but the corpus never pinned: object/array/tuple declarations (renames, defaults, holes, nesting, fresh rest tails), the options-object parameter pattern (element and whole-pattern defaults, tuple/rest parameter patterns), for-of heads (containers, defaults, rest, existing bindings), object rest freshness and key order, assignment basics (swaps, chains, expression value), and evaluation order (RHS once, elements left to right, lazy defaults that see earlier bindings).
- All byte-identical against Node under both backends, and each reports 100% static coverage.
2026-07-23 17:27:33 -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