Commit Graph
539 Commits
Author SHA1 Message Date
Chris Tate fed7193648 Consumers take the named fence for provably-non-stream arguments
- Node's consumers reject asynchronously with the plain not-async-iterable TypeError; no sync ladder exists to borrow, so the provable case refuses with the rejection domain named
2026-07-24 17:21:12 -05:00
Chris Tate 6c4430e0de Bare 'module' resolves the builtin like node:module
- Node serves the builtin for both spellings, so both key the same lowering tables; the stale prefix-only caution predated the createRequire erasure
2026-07-24 17:18:36 -05:00
Chris Tate de37b09716 Trap-claim carve-outs restore two over-narrowed shapes; extending a poisoned base refuses at build time
- The written-binding decline yields when the declared type has no mapping or the write's RHS roots at an ambient-undefined name
- A class extending a collection-poisoned base flushes the base's diagnostics eagerly: the heritage evaluates at module init, so deferring the compiler's own fence manufactured a silent wrong output
2026-07-24 17:18:25 -05:00
Chris Tate 6388ffdd52 pipeline argument ladders throw Node's errors instead of escaping the internal marker
- Both pipeline lowerings catch the arg-type marker the finished paths already caught; provably-invalid stages throw Node's synchronous ERR_INVALID_ARG_TYPE in every position
- Accepted-but-unlowered sources fence naming what Node accepts; finished(null) joins the isNodeStream ladder
2026-07-24 17:17:15 -05:00
Chris Tate e947502af6 The long tail of engine-held values: iteration, promises crossing, and the driver through its plugin pipeline
- for-of, destructuring, and spread over engine-held dyn values ride the engine's own iterator protocol with V8's exact non-iterable spellings; for-of over dyn lowers generally
- Static promises cross into the engine as thenables; Promise.all over mixed and non-literal entries runs the engine's own combinator; engine-array fulfillments exit by reference
- The re-minted silent-wrong-answer path is closed: island-literal members whose values poison fence at the read with the captured diagnostic
- The checked-dynamic tree replaces the old nickname across comments and internal docs
2026-07-24 17:17:00 -05:00
Chris Tate ad3a7a830b Bare 'module' resolves the builtin like node:module
- Node serves the builtin for both spellings — a builtin wins over a same-named npm package for the bare specifier — so both key the same lowering tables
- The stale prefix-only caution predated the createRequire erasure; the hint's claim and the behavior now agree
2026-07-24 15:25:47 -05:00
Chris Tate a7cd284944 Trap claims survive writes storage cannot serve; extending a rejected base reports at compile time
- trapDeclRootOf's written-binding decline now requires a mappable declared type: ordinary storage only buys anything when it compiles — an unmappable recursive call shape traded the compiling no-storage trap for a guaranteed SC2009 on a value that never exists
- bindingEverWritten skips statement-position writes whose RHS chain roots at an ambient-undefined name: that statement IS the RHS root's throw (lowerExprStatement's first claim) and never touches the target's storage, so it cannot justify the decline
- A poisoned base a top-level class extends flushes its deferred diagnostics eagerly (collectClassShape): the heritage evaluates at module init and the base's fence is the compiler's own — Node defines the base fine — so the deferred trap compiled a binary that refused at startup where Node runs; leaf and derived-only poisoned classes keep the JS deferral
- Fixtures: corpus 2632 (unmappable written trap binding) and 2633 (ambient-rooted rewrite), diagnostics js-extends-poisoned-base (the eager report names the base's real blocker), errors.test pins for both deferral edges that must NOT report eagerly; order-parity baselines recorded for the new entries
2026-07-24 15:03:30 -05:00
Chris Tate 72ccd05c34 pipeline's stage ladder throws Node's body clause instead of crashing the compiler
- lowerStreamArg's arg-type marker escaped both pipeline lowerings uncaught (only finished caught it), so a compile-time non-stream stage — pipeline('', dst, cb) — crashed the CLI with a raw StreamArgTypeThrow; both pipeline paths now catch and answer it as the call's value, matching finished
- The ladder splits by API to match Node exactly: finished keeps the isNodeStream clause (null now included), while pipeline stages render makeAsyncIterable's "body" clause — the same text in source, middle, and destination positions — for numbers, booleans, null, and pair-less records
- Shapes Node accepts but scriptc can't lower yet never take a throw Node never throws: strings and arrays (iterables) and records carrying a readable/writable member (the duplex-pair form) keep the pointed fence, whose hint now names those inputs
- tests/corpus/2632-stream-pipeline-arg-ladders.cjs pins the ladder in all three positions plus finished(null) and a live pipe, byte-identical through both backends in the plain and sanitized lanes
2026-07-24 12:48:41 -05:00
Chris Tate e96d1264c1 Release 0.0.11 v0.0.11 2026-07-24 12:47:01 -05:00
Chris Tate 78177039a4 The jsval-element-array exit stays on its own targets: JSON-safe arrays keep the round trip, any[]-declared locals keep the island handle
- The jsExit emitters' array arm intercepted EVERY array target — string[] exits (the rest-args map/join idiom, corpus 2449) came back as handle-element arrays and native join trapped. Both backends now gate on the jsval element kind; JSON-safe element types keep the dynCheck round trip.
- An island value under an any[]-declared LOCAL stays an island HANDLE (the runtime-world rule, the package-promise local's twin): the exit is a CALL-boundary conversion — a converted local would strand the engine's own prototypes where the handle routes every use.
2026-07-24 12:25:34 -05:00
Chris Tate 9f85b2cdfd Record order-parity baselines for the six-lane train 2026-07-24 12:16:32 -05:00
Chris Tate 07c9a4128c node:stream/consumers compiles statically; createRequire erases to static requires
- text, json, and buffer settle real promises over the readable machinery with Node's settle timing and rejection set; arrayBuffer and blob fence with their representation reasons
- createRequire over this file's URL is compile-time plumbing: builtin specs become namespace imports, relative JSON bakes as validated documents, npm packages ride the require-condition graph, unresolvable names throw Node's catchable MODULE_NOT_FOUND
- module.builtinModules bakes as the compat target's exact list

# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	packages/compiler/src/frontend/lowering/lowerer.ts
2026-07-24 12:13:08 -05:00
Chris Tate aac4f9390b Buffer views alias for real; stream ticks interleave with process.nextTick in Node's order
- Buffer slice and subarray become true aliasing views over the retained owner — mutating through a view was silently computing wrong bytes
- Each stream tick posts a one-to-one marker on the user tick queue, so stream emissions interleave with process.nextTick in enqueue order, Node's order by construction; the station remains for teardown
- Stream emitters model the pre-created event-key shape governing eventNames order; push honors defaultEncoding with Node's unknown-encoding errors

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-24 12:11:31 -05:00
Chris Tate 794b35a26e Subclasses override emit in the forwarding shape, monomorphized per event
- The forwarding shape — emit(event, ...args) whose rest appears only as super.emit(event, ...args) — lowers per dispatching event as a real hierarchy method, vtable-participating
- error events keep the throw-when-unhandled path through overrides; super.emit resolves to the nearest override ancestor, JS's chain
- Receivers above every override route through interned instanceOf dispatch; other emitter members and stream-rooted classes keep their fences with reasons stated

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-24 12:10:30 -05:00
Chris Tate 034195f588 Profile-declared integer boundary slots prove or refuse at compile time
- An interval-and-wholeness inference over the lowered IR proves integrality and range for every value reaching a declared i64/u64 slot, with loop-header widening precise enough that the bounded counter loop proves its exact range
- Refusals carry the slot path, the failed obligation with evidence, and the author's concrete fix as SC4021, SC4022, and SC4023
- Declared slots are both obligation and assumption: call sites check arguments, callee bodies assume their classes; integer returns emit exact machine-integer wrappers and inbound params trap through the structured host-contract family
- Math.trunc and Math.ceil compile statically

# Conflicts:
#	packages/compiler/src/library/profile.ts
2026-07-24 12:07:29 -05:00
Chris Tate 328ccffbc8 The fence taxonomy covers the attestation's ground; profile roots refuse unknown keys
- Twenty-four manifest ids project the Date, performance, and process ambient families, so full fences are declarable and the determinism invariant is structural
- Six scan corrections: live ambient reads the attestation missed now demote, with paired fenceable ids; a parity test enforces the pairing permanently
- Unknown profile root keys refuse; ask-5 keys at the root get the relocation message
2026-07-24 12:06:37 -05:00
Chris Tate 092220ec51 Collect-phase probes recover from poison; no-storage bindings claim statement declarators only
- The ambient-trap probe resolves as a side-effect-free probe: deferred collection diagnostics stay deferred, and the merged-namespace fence lands as a diagnostic instead of crashing the collect walk
- deadUnmappableBinding and the nullish family claim statement-position declarators only; for-init heads and catch bindings stay locals
- Trap classification declines written bindings; the seven 0.0.10 pass-to-fail fences restore byte-identical to 0.0.9 behavior
2026-07-24 12:06:37 -05:00
Chris Tate f4206bd464 Restore the seven 0.0.10 pass-to-fail regressions by narrowing the ambient/nullish recognition
- ambientUndefVarRootOf resolves as a side-effect-free PROBE (the collect-phase guard): bare resolution flushed reached declarations' deferred collection diagnostics onto the build — failing three JS programs at compile time where the JS-input design defers fences to runtime (checkJsFiles_noErrorLocation, multipleDeclarations, jsdocTemplateClass), fencing tsxDefaultImports' enum-carrying static field through an import alias, and double-reporting the checker-panic fixture — and threw the cross-block merged-namespace fence's PoisonError out of every probe site
- Trap classification declines WRITTEN bindings (trapDeclRootOf): the no-storage trap fenced later assignments ("assignment to 't1' (not a writable local or module global)", literalWideningWithCompoundLikeAssignments) where ordinary storage compiles them; the initializer read still lowers to the root's ReferenceError, so the writes never run either way
- The nullish no-storage family claims only the EMPTY interned record shape: a field-bearing record binding (`const value: { inner: number | string } = null as any`) keeps real storage — its comma-chain and call-argument reads have no no-storage lowering (narrowCommaOperatorNestedWithinLHS, varianceProblingAndZeroOrderIndexSignatureRelationsAlign)
- classFieldSuperAccessibleJs2 reverts with the shared mechanism to its 0.0.9 state: the build passes and the binary still refuses at runtime with the same SC1090 (the honest JS runtime fence, not silent wrong output)
- Fixtures: corpus 2609 (trap binding with later writes), 2610 (field-bearing nullish record), 2611 (JS deferred-collection probe), 2612 (enum static field through a default import); checker-panic-infinity snapshot returns to its single pre-0.0.10 report
2026-07-24 11:57:39 -05:00
Chris Tate 96ca45081d Drop an unused eslint directive in the ask-4 suite 2026-07-24 11:52:54 -05:00
Chris Tate 2c8e31b1ff Rendered snapshots for the integer-boundary refusal codes; dynamic-surface fixture re-based
- library-int suite snapshots SC4021/SC4022/SC4023 rendered end to end (slot path, evidence, span, fix) — the diagnostics-corpus discipline for codes the executable lane cannot reach
- dynamic-surface diagnostics fixture swaps Math.ceil for Math.sqrt (trunc/ceil compile statically now) and its snapshot regenerates
2026-07-24 11:52:12 -05:00
Chris Tate 01b47dd78b Promise.all over non-literal 'any[]' arguments runs engine-side; island values bridge into Promise-typed slots with the any[] settle payload
- The loadPlugins shape end-to-end: Promise.all(plugins.map((p) => loadPlugin(p))) — a NON-literal argument the checker spells any[] — lowers to the ENGINE's own Promise.all over the lifted array, and the INFERRED Promise<any[]> return coerces through the island→static promise bridge with the new SCR_ISLP_JSVAL_ARR settle payload: the fulfillment exits Array.isArray-gated BY REFERENCE at the settle, a lying fulfillment rejecting with the boundary TypeError (trust-but-verify at the settle).
- coerceToExpected's jsval branch gains the promise-slot arm generally: island values flowing into Promise<void>/Promise<any>/Promise<any[]>-typed slots bridge instead of fencing (the passing-a-package-promise-into-promise-typed-parameters fence retires for these inners); other inners keep the named fence.
- Corpus 2605 extended with the non-literal all + inferred-return rows, byte-identical to Node under both backends and SAN-clean; dyncheck/errors/island suites green.
2026-07-24 11:51:34 -05:00
Chris Tate a2c68beadb The ask-4 conformance corpus end to end, with the sidecar attestation and inbound-trap fixtures
- tests/harness/library-int.test.ts: all 16 reference-corpus cases as library fixtures on both emissions — send/sendU64 are profile-mapped exports with i64/u64 params, PROVE cases pin every crossed value against the Node oracle (the test process runs the same case source), the counter loop crosses 0..9 in order, REFUSE cases name obligation, slot path, and evidence
- int-returns fixture: i64/u64 returns cross as real int64_t/uint64_t (9007199254740991, -0 as 0, -7 % 3 as -1, ToUint32 max on a u64 slot)
- int-trap fixture: inbound integers past 2^53-1 deliver the structured SC4012 host-contract trap with profile teaching/remediation; in-range extremes convert exactly
- Sidecar slots: integer_slots attestation in declaration order, declared slots spelled i64 (V10 bijection validated), unproven writes refused by slot path (Msg.count, helpers.clampIdx.return), unresolvable and non-number declarations refused SC4009, teachings ride SC4022 as attributed notes
- Layer-1 differential: no Layer-1 representation exists (trivially unobservable); pinned consequence — f64-declared vs i64-declared builds cross byte-identical values
- Integer-declared helpers join libRoots so their attestation covers a compiled body, never a dead-stripped vacuity
2026-07-24 11:39:43 -05:00
Chris Tate 5950d3f12b Demote the live machine-state reads the attestation missed, each paired with a fenceable manifest id
- process.resourceUsage, cpuUsage/threadCpuUsage, constrainedMemory, and the stdio isTTY/columns reads are live ambient state the attestation attested past; their spellings now demote deterministic (flipped attestations are corrections), and each family projects a node-builtin.process.* manifest entry with a reach-witnessing detector, so the §4 invariant holds by construction.
- The dead process.memoryUsage prefix is gone from the scan table, replaced by a comment saying why nothing can match it (no lowering spells it) and what a future lowering owes the table and the projection.
- The parity test covers the new prefixes through the existing node-builtin.process. family fence; the resolve test pins the new ids in the family sweep, and a K14 case pins the teeth end to end: process.stdout.isTTY under the full-fence profile refuses SC4008 naming node-builtin.process.isTTY, both emissions.
- surface-manifest.json regenerated (6 new entries, 342 total).
2026-07-24 11:39:39 -05:00
Chris Tate 1c3324fb63 Fix both 0.0.10 crash sites: collect probes recover from poison, for-init declarators stay locals
- collectGlobals' trap/nullish/dead classification probes catch PoisonError per the per-probe recovery discipline: the cross-block merged-namespace SC1090 fence reached through the ambient-root walk recorded its diagnostic and then crashed the CLI mid-discovery (5 merged-namespace corpus tests, ice bucket)
- On a poisoned probe, JS files defer the recorded diagnostics to runtime fences (deferJsCollectionDiags) and the binding falls through to ordinary registration; the statement lowering re-fences at its own site
- The no-storage binding families (nullish, dead-unmappable) only claim statement-position declarators: an uninitialized for-init let (`for (let x; false;) {}`) was claimed as dead-unmappable, violating lowerVarDeclList's contract that a for-init declarator always lowers ("lowerer bug: for-init declarator resolved to a global", 2 corpus tests)
- Fixtures: corpus 2608 (for-init heads with the masking variants), diagnostics merged-namespace-collect-probe (the collection-reached fence stays an honest SC1090)
2026-07-24 11:38:39 -05:00
Chris Tate c1be329607 Static promises cross into the island as real engine thenables; Promise.all over mixed island/static entries runs the engine's own combinator
- jsvalIn (and the island-literal member path behind it) accepts STATIC promises whose fulfillment is in the reverse bridge's payload domain — void/number/boolean/string/'any', plus the NEW Promise<any[]> payload (SCR_ISLP_JSVAL_ARR: a native array of engine cells re-enters as a fresh engine array over the SAME engine values). scr_jsval_from_promise emits from jsMarshal in both backends; the IR validator admits the arm.
- Promise.all with MIXED entries where some entry is an island value (the withPlugins shape: Promise.all([loadBuiltinPlugins(), loadPlugins(plugins)])) lowers to the ENGINE's own Promise.all — island entries pass through, static promises bridge as thenables, and the combined result stays an island value the static side awaits through the existing island→static bridge. All-static tuples keep the typed fence hint.
- Corpus 2605 (unawaited static promise in an 'any' slot answers typeof "object" and awaits engine-side; the mixed-all + .flat() chain) is byte-identical to Node under both backends; SAN clean. Composite fulfillments (Promise<{...}[]>) stay fenced with the promise type named — the payload-domain residue.
2026-07-24 11:32:57 -05:00
Chris Tate 10696c70f8 Profile-declared i64/u64 integer slots: surface, wiring, wrappers, host-contract trap
- LIB_PARAM_CLASSES/LIB_RETURN_CLASSES grow i64/u64 (the prove-or-refuse pair); u8/u32/i32 stay param-only with the message updated to point at the landed machinery
- sidecar.integer_slots declares contract slots by sidecar path (Msg.count, Point.x, helpers.f.params[0], helpers.f.return); the projection spells each declared slot i64 (the frozen format-1 vocabulary — u64 is the stricter compile-time obligation over the same wire spelling), refuses non-number or unresolvable paths, and emits integer_slots as the resolved-decision attestation in declaration order
- compileLibrary runs the integer-boundary inference over export-map and sidecar-declared slots and refuses SC4021 (representability) / SC4022 (wholeness) / SC4023 (range), each carrying slot path, failed obligation with evidence, and the author's fix; u8/u32/i32 plumbing classes seed parameter shapes for free precision
- Both emitters grow i64/u64 wrappers: inbound params range-check through scr_library_i64_in/u64_in (the SC4012 host-contract trap — silent rounding past 2^53 is a coercion the author never wrote), returns convert exactly under the discharged proof
- Profile-loader suite pins the new surface; the pre-ask-4 pins updated
2026-07-24 11:29:30 -05:00
Chris Tate fe40a0f904 Refuse unknown profile root keys: a fences array pasted at the root was silently inert
- The ask-5 keys (fences/teachings/remediations) at the profile root refuse SC4001 with a pointer to their determinism.* home; every other unknown root key refuses with the standard unknown-field wording.
- The root goes strict everywhere, not just for the three ask-5 names: profiles pin per compiler release, so an unknown root key is a typo or a misplaced section (a misspelled sidecar would silently drop the contract), never forward-compat surface. All fixture and harness profiles load unchanged under the strict root.
- The check runs after the profile_format gate so a future format still refuses as the wrong format, and the interior of determinism keeps its reserved-and-ignored surface beyond the three ask-5 keys, both pinned by tests.
2026-07-24 11:26:34 -05:00
Chris Tate 165ca1f125 Project the attestation's ambient ground into the surface manifest so full fences imply deterministic
- The determinism attestation demoted on Date/perf_hooks/process libCalls that had no manifest entry, so the ask-5 §4 invariant (compiles under full fences => deterministic) could not be stated and the spec's own worked-example prefix stdlib.date. refused as unknown; AMBIENT_SURFACE_FNS now projects those families as permanent manifest ids (stdlib.date.*, node-builtin.perf_hooks.performance.now, node-builtin.process.*) with reach-witnessing detectors.
- BUILTIN_MODULE_FN_ALIASES folds the special-cased alternate spellings into their member detectors: the fs Buffer/fd/options/withFileTypes/watch-listener variants, the JS-ladder mkdtempSyncChk and lchmodSyncChk, crypto's composed randomBytesToString, and os.userInfo's userHomedir/userShell scalars.
- A parity test holds the two scans together over the exhaustive IrLibFn registry: every spelling LIB_NONDETERMINISTIC_PREFIXES demotes on must be deniable through a manifest-id fence, with the library-mode-refused and validate-then-fence residue pinned by name with reasons.
- The §4 invariant fixture and K14's worked fences now cover every attestation-known family, and a new K14 case pins the teeth: Date.now / process.env reads under the full-fence profile refuse SC4008 naming the entry instead of attesting deterministic: false post hoc.
- Sampling probes cover the new static entries (Date.now, process.pid, performance.now); surface-manifest.json regenerated (18 new entries, 336 total).
2026-07-24 11:25:42 -05:00
Chris Tate 6b6445ab90 Retire the "DOM" nickname in comments, internal docs, and message prose: the checked-dynamic tree / dyn values
- The ratified rename rider (world-unification §7 lane 5): "DOM" collided with the browser DOM and, post-lane-1, the structure is no longer a document tree. Comments and doc prose now say "the checked-dynamic tree" for the noun and "dyn" adjectivally; runtime symbol names (scr_dyn_*), the error.domClone libCall, and the real web DOMException family are unchanged.
- The few user-facing strings carrying the nickname move with it (the structuredClone cyclic message, the dc subscriber/publish fences, two emitter ICE texts); diagnostics snapshots regenerated (96 pass). One private compiler constant (DOM_PROTO_METHOD_NAMES -> DYN_PROTO_METHOD_NAMES) rides along.
- Mechanical sweep, 142 files, comments/prose only — no behavior change.
2026-07-24 11:14:58 -05:00
Chris Tate afa63317f8 Record order-parity baselines for the new corpus fixtures; the dataview fence names both .buffer consumers
- order-parity.json gains 2604-2607 entries (SCRIPTC_UPDATE_BASELINES re-record)
- The '.buffer' SC1090 message now names new DataView(x.buffer, ...) and Buffer.from(x.buffer, ...) as the composed positions and states the subarray-view alternative; snapshot regenerated
2026-07-24 11:13:38 -05:00
Chris Tate 9e21ef5cc3 Record order-parity baselines for the cycle and named-group fixtures 2026-07-24 11:12:25 -05:00
Chris Tate b98d726382 for-of, destructuring, and spread over island values held in 'unknown' drain the engine's own iterator; engine arrays exit into 'any[]' slots
- The iteration pack's JSVAL arm is ARMED (scr_dyn_iter_pack, scr_dyn_arr_push_spread, scr_dyn_pack_push_spread_iter route through the new iter_drain ops row): the ENGINE's iterator protocol drains into a fresh DOM array — engine arrays, generators, Maps, Sets, and Symbol.iterator implementations step exactly as Node runs them, elements wrapping back scalar-normalized.
- for-of over checked-dynamic values LOWERS now (both backends): the source packs once through dyn.iterPack and a hidden index loop binds each element as a dyn value (new dyn.arrLen/dyn.arrAt accessors) — an eager snapshot, the matchAll stance. Pattern heads, pre-declared and var heads, and labeled break/continue all ride the desugar. Non-iterables throw V8's for-of CallPrinter spellings, threaded into the engine-side guard.
- The validated exit gains the jsval-element-array arm (scr_jsval_exit_jsval_arr, both backends): an 'any' value exits into 'any[]'- and 'any[] | undefined'-declared slots Array.isArray-gated, elements BY REFERENCE (identity crosses, the spine is a snapshot) — the withPlugins loadPlugins(plugins) boundary, the driver frontier's first named poison.
- Corpus 2604 and npm case jsval-iterate (package-minted Set/Map/generator/Symbol.iterator) are byte-identical to Node under both backends; dyncheck's jsval-iterate pin flips from the retired island fence to the armed answers; SAN clean over the drain/wrap/exit churn.
2026-07-24 11:04:10 -05:00
Chris Tate 7fe38cc085 Island-literal member fences follow the member's shape: data members fence at the READ, not as callables
- A member value an island ('any'-typed) object literal cannot lower or marshal used to become a call-time fence CLOSURE regardless of shape — a data-shaped member (the withPlugins plugins: slot) then answered typeof "function" and .length 0 silently, re-minting the fence box entry 394 retired, and downstream errors blamed the wrong thing (plugins.flatMap is not a function).
- Data-shaped members now define through the engine's getter machinery instead: READING the member throws the captured diagnostic verbatim — the honest granularity, since using the value is exactly what cannot be answered.
- Function-shaped members (syntactic lambdas/methods, checker-callable values — the doc-builder aggregate and commander wrapper shapes) keep the ratified call-time closure, as do conditional-spread literals where getters cannot combine.
2026-07-24 11:03:23 -05:00
Chris Tate ced8f52207 The integer-boundary inference over the lowered IR, with the reference corpus as IR-level unit tests
- library/int-infer.ts: interval+wholeness+maybeNaN+spelling domain, JS-exact transfer functions (ToInt32/ToUint32 bitwise contract, JS remainder sign, Math rounding family discharges wholeness), branch refinement on both edges (ordered comparisons exclude NaN; wholeness sharpens strict bounds), loop-header-only threshold widening with body-edge recovery
- Structured-IR fixpoint: scoped walk with join points at if/switch/try merges, per-loop header fixpoint plus a collect pass, break/continue env folding, transitive mutated-global summaries so direct calls havoc precisely
- Interprocedural v1: intraprocedural with declared-slot summaries (declared params seed their class shape; declared returns are assumed at call sites, proven at their own returns)
- numLit carries the source spelling exactly when a decimal integer literal does not round-trip f64 (the representability witness)
- All 16 reference-corpus cases pass at the IR level, the bounded counter loop proving exactly [0, 9]
2026-07-24 11:03:14 -05:00
Chris Tate a44f5b3994 Renumber the named-group fixtures past the cycle fixtures 2026-07-24 10:50:49 -05:00
Chris Tate 071e5644fd Named capture groups compile end to end
- The pattern fence lifts: the vendored engine already executes named groups and named backreferences
- Match results project .groups at compile time as the checker's own record shape, with Node's key order, undefined-when-none, and null-read error texts
- Replace and replaceAll resolve $<name> templates with Node's exact edge behaviors; matchAll rows carry groups through for-of, drains, and destructuring
- Destructuring over index-signature records reads through the overflow map generally
2026-07-24 10:50:22 -05:00
Chris Tate 6c3de6befe Benign import cycles compile under the dynamic tier too; refusals narrow to the binding
- The dynamic-import subgraph judges back edges with the same admission engine as the static graph; its refusals now reach the build instead of being discarded with the discovery pass
- TDZ-shape reads and non-inert top levels refuse naming the binding or statement and its location, not the whole cycle
- Evaluation order stays Node's postorder with back edges as cache hits, pinned by the parity baselines
2026-07-24 10:50:07 -05:00
Chris Tate f28c271784 Math.trunc and Math.ceil compile statically
- New libCalls math.trunc/math.ceil: C trunc()/ceil() are the JS operations, llvm.trunc.f64/llvm.ceil.f64 on the LLVM lane
- Joined STATIC_MATH_FNS (checked before the island table) — they are ask-4's wholeness-discharge operators, so library builds need them compiled
- Surface manifest regenerated: stdlib.math.trunc/ceil move to static
2026-07-24 10:45:01 -05:00
Chris Tate 65bab3d8cd Named capture groups compile end to end
- lowerRegexLiteral drops the (?<name> fence: patterns and \k<name> backreferences run natively in libregexp, and .groups on exec/match/matchAll results desugars into a compile-time record projection over the honest slice — the checker's own Record<string,string> hybrid with overflow entries in group declaration order — whenever the regex is statically known (literals, const bindings, new RegExp("..."), matchAll rows via for-of bindings, stored drains, spread rows, element access)
- Node's exact shapes: a group-less regex answers .groups === undefined, nullable receivers throw Node's TypeError on the unit arms, ES2025 duplicate names project the participating alternative, and destructuring rides — flat, aliased, and the nested { groups: { year } } pattern — via the groups wrap plus a general overflow read for undeclared keys of index-signature shapes
- scr_put_substitution resolves $<name> against lre_get_groupnames with Node's edge answers: nonexistent and nonparticipating names substitute empty, $< stays literal when the pattern has no named groups, an unterminated $<name stays literal
- s.match(re) also claims a nullable string receiver (the Dict<string> member spelling) through the checked narrow; an untraceable .groups read keeps the SC2020 fence with a hint naming the traceable shapes
- Differential corpus 2604-2607 (both backends, SAN), runtime C substitution/backreference cases, refreshed diagnostics snapshots and surface manifest; corpus 1614's deliberate deferred fence moves to a Proxy wrap now that hybrid record reads compile
2026-07-24 10:41:37 -05:00
Chris Tate 08de805fb5 Record order-parity baselines for the emit-override fixtures
- Purely additive: the eight new entries (corpus 2604-2610 and the ee-emit-override diagnostics program) join baselines/order-parity.json; no existing baseline moves.
2026-07-24 10:34:13 -05:00
Chris Tate 4abd8e4fff node:module compiles: createRequire's static erasure and the baked builtinModules
- createRequire(import.meta.url) (or __filename / import.meta.filename) in a const binding is compile-time plumbing that emits nothing; each require call through it resolves per site against the binding's own file, and the inline createRequire(import.meta.url)("spec") spelling erases identically
- The erasure per target: a builtin spec makes the binding a namespace import in const clothing (namespace, destructured, and inline-member spellings all key the existing module tables, with the fallback-typed 'as typeof import(...)' cast idiom stripped); a relative .json document validates at compile time and lowers to json.parse over its baked text (JSON.parse's checked-dynamic unknown stance — exactly Node's value); a bare name nothing installed resolves compiles to Node's catchable MODULE_NOT_FOUND throw (the optional-dependency try/require pattern); an installed npm package embeds under --dynamic through the island's NEW require-condition entry (NpmGraphBuilder.addRequire — a dual package answers its CJS arm, exactly Node's createRequire) and reports the requires-dynamic diagnostic in a static build
- Dynamic specifiers, '#' project imports, relative non-.json targets, --npm-static opt-ins, and non-this-file bases all fence by name; the island-handle rescue skips the plumbing decls so --dynamic builds erase them too
- module.builtinModules bakes as Node v24's exact list (a fresh string[] per read, pinned to the compat target so emitted programs stay host-independent); isBuiltin and syncBuiltinESMExports fence per site with pointed hints, and the createRequire hint, its diagnostics snapshot, the surface manifest, and the npm-static offender wording follow
- Corpus 2606 pins json/builtin/builtinModules/MODULE_NOT_FOUND differentially on both backends and under ASan; the npm create-require fixture case pins cjszoo's module.exports, dual's require-condition CJS arm, and the missing-package rejection against Node
2026-07-24 10:32:20 -05:00
Chris Tate 6f2de332db Readable push encodings: defaultEncoding decodes string pushes, explicit literals override, unknown spellings take Node's ERR_UNKNOWN_ENCODING
- The Readable defaultEncoding option lowers to a push-side decode default (Buffer.from(chunk, enc)); push(chunk, enc) with any literal encoding decodes per call and overrides the default, utf8 included
- Unknown literal encodings become Node's runtime TypeError — at construction for the encoding/defaultEncoding options (nodeThrowExpr, the ladder pattern), at the call for push
- on/once/off admit Node's ignored extra arguments when they are pure reads (the suite's stray-third-argument shape); effectful extras keep the fence
- readable.pushStrEnc/readable.pushEncoding land in both backends; corpus 2607-stream-push-encodings pins the decode, override, and both throw sites differentially
2026-07-24 10:30:47 -05:00
Chris Tate 567af934f1 Release 0.0.10 v0.0.10 2026-07-24 10:03:40 -05:00
Chris Tate 2785bb09f9 Stream ticks ride the user nextTick queue: FIFO with process.nextTick, Node's order
- Every deferred stream emission posts a raw marker on the user tick queue (scr_next_tick_raw) and each marker dispatches exactly one stream tick, so resume_/emitReadable_/endReadableNT interleave with user nextTicks in enqueue order — in Node they are the same queue
- Cures the setEncoding-with-buffered-bytes shape: a nextTick registered after on('data') now observes the flowed chunks, and stream emissions run before microtasks at each checkpoint instead of after
- The loop station stays as the drain for anything a marker never reached and as the uncaught-throw cleanup of undelivered ticks
- Corpus 2606-stream-ticks-are-nextticks pins the interleavings (buffered decode, split multi-byte char, tick/data/tick ordering, end-after-tick) differentially on both backends
2026-07-24 09:47:43 -05:00
Chris Tate c1a800dbad The JS lane's unannotated emit override lowers in the forwarding shape
- An unannotated event parameter (checker any) in a JS source binds as the string it always is — event names are compile-time literals program-wide — so CJS emitter subclasses wrapping emit compile like their TS twins; TS sources still spell the annotation.
- Corpus 2610 pins the CJS override with filtering, instance state, and checked-dynamic listeners against Node on both backends.
2026-07-24 09:41:21 -05:00
Chris Tate 6e9467821f Stream emitters model Node's kShapeMode: pre-created event keys govern eventNames order
- Stream constructors reserve their class's known _events names (Readable close/error/data/end/readable, Writable close/error/prefinish/finish/drain, Duplex the union) — empty reserved buckets are invisible to every read but hold the first-registration rank, so eventNames lists them before earlier user events, exactly Node's shape optimization
- Shape mode reproduces Node's removal split: removeListener keeps an emptied name's rank (events[type] = undefined), the named removeAllListeners deletes a populated key (and resets wholesale when the count drains, kShapeMode quirk included), the no-argument wipe resets everything and leaves shape mode
- Corpus 2605-stream-event-names pins the order, rank survival, drain, and wipe behaviors differentially on both backends
2026-07-24 09:40:01 -05:00
Chris Tate e47ad23c0c Subclasses may override emit in the forwarding shape, monomorphized per event
- class C extends EventEmitter may re-declare emit(event: string, ...args: unknown[]): boolean when the rest parameter only forwards through super.emit(event, ...args) in the method's own body; the body lowers once per event name as the real hierarchy method emit:<event> (vtable participation, (this, ...tuple) => bool ABI), with event bound to the name and the forward lowered to the runtime emit — or to the nearest override ancestor's specialization, JS's super chain.
- Emit sites dispatch by the whole-program rule: receivers at-or-below an override call the specialization directly (virtually when a deeper override exists); receivers above every override route through an interned helper that instanceOf-tests the topmost override subtrees and falls through to the runtime emit, so plain EventEmitter bindings holding override instances still dispatch like Node.
- super.<member> over an emitter-rooted base now lowers through the emitter spoke with static dispatch (an override at-or-below the lexical class never answers a super call); registering 'newListener'/'removeListener' listeners fences while a comparable class overrides emit (the runtime fires meta events internally, past the override), and every other emitter member keeps the surface fence with the forwarding-shape rule spelled out for emit.
- Corpus 2604-2609 pin both backends against Node: the logging override with upcast dispatch, two-level override chains with literal super.emit, once/off/prepend ordering and handled errors under an override, the unhandled-'error' throw through an override, the filtering override, and an emitter-subclass job queue; the diagnostics snapshot pins the three fences.
2026-07-24 09:37:57 -05:00
Chris Tate f2a629ae06 The --dynamic import() subgraph rides the shared cycle admission
- Extract preflight's benign-cycle admission (the cheap per-edge rule plus the declaration-only init window analysis, with Tarjan SCC and cluster-verdict memos) into makeCycleAdmission; preflight's static walk and appendDynamicImportModules now judge back edges identically, so benign cycles behind dynamic import() compile as Node cache hits instead of blanket SC1016.
- appendDynamicImportModules runs once in lowerToIr before any pass constructs: the old per-constructor run minted cycle refusals into the discovery pass, whose diagnostics are discarded by design, so inadmissible import() cycles silently compiled; refusals now land on the emit pass with the same narrowed reason (binding and line, or the offending top-level statement) the static walk mints.
- SC1016 gains a registry hint naming the admitted shape and the fix; diagnostics snapshots, order-parity baselines, and the surface manifest repin with it.
- Corpus 2604 (mutual class cycle across two modules), 2605 (three-module function ring with two back edges and a function-body-only value use), 2606 (import() into an admitted mutual-recursion cycle) — all byte-identical to Node through both backends; diagnostics fixture dyn-import-cycle-tdz pins the dynamic-path TDZ refusal.
2026-07-24 09:32:53 -05:00
Chris Tate 135aec0383 Buffer slice/subarray become true aliasing views; typed arrays gain element fill and Buffer.from(x.buffer)
- scr_bytes_subarray: a same-elem view retaining the owner (the DataView rule, chain depth 1); subarray() and Buffer's slice() lower there, plain typed arrays' slice() keeps the copy — mutations through views are visible both ways now, byte-exactly with Node
- Buffer.from(x.buffer, byteOffset?, length?) lowers as the dataViewNew view construction (the syntactic .buffer peel); the fallback lib gains the ArrayBuffer overload
- fillElem: TypedArray.prototype.fill on non-u8 receivers, per-element coercion with slice-clamped indices; u8 keeps the Buffer fill family
- Both backends emit the new intrinsics; corpus 2604-bytes-views pins aliasing, offset composition, swap-through-slice, clamping, and fill coercion differentially
2026-07-24 09:28:37 -05:00
Chris Tate 417ece6724 node:stream/consumers compiles statically: text, json, and buffer settle real promises
- scr_sc_text/json/buffer accumulate every chunk through a native 'data' listener (string chunks as their utf8 bytes, the Blob rule) and settle at the terminal point through the finished watcher — Node's own eos timing, verified byte-exact including resolution landing after 'close'
- Rejections match Node's set: the stream's error, ERR_STREAM_PREMATURE_CLOSE on an early close, json's SyntaxError on malformed text, and the async-iterable TypeError for a stream with no readable side
- lower-stream's stream/consumers arm lowers text/json/buffer over one lowered stream argument (json answers a checked-dynamic promise — the JSON.parse unknown stance); arrayBuffer and blob fence module-qualified with pointed hints, since neither value has a representation
- Both backends emit sc.text/sc.json/sc.buffer; the trio joins the may-throw seeds, the loop-liveness set, and moduleUsesStream's link gate; the SC1010 hint, its snapshots, and the surface manifest gain stream/consumers
- Corpus 2604/2605 pin the consumers differentially on both backends and under ASan + RC audit: split multibyte sequences across chunks, encoded string-chunk streams, settle ordering, empty streams, and all four rejection arms
2026-07-24 09:27:44 -05:00