Skip to content

P8 - language modernization - #118

Merged
toshok merged 25 commits into
mainfrom
language-p8
Aug 2, 2026
Merged

P8 - language modernization#118
toshok merged 25 commits into
mainfrom
language-p8

Conversation

@toshok

@toshok toshok commented Aug 1, 2026

Copy link
Copy Markdown
Owner

The 0.1.0 release happened in 2011, back in the ES5 days. 0.2.0 brought us forward in most areas, but not language version. This PR does that.

Also add a --script command line flag to toggle strict mode (the default) to sloppy at the toplevel.

We now pass 74.8% of test262 (previously was 54.5%). The increased language support means we can run the static analysis in stage2/3, which is awesome.

Fable's output below:


How it went: I clustered the ~2,700 baseline failures by normalized error message rather than feature tag, which exposed a handful of choke points, then worked them in three waves (with 8 parallel subagents on disjoint runtime files, serialized through a buck2 lock):

  • Attributes & function identity — every builtin now has spec-correct own .name/.length (arities stamped from a generated table), non-enumerable methods, spec-attribute constants and .prototypes, and builtin methods are no longer constructors.
  • Error semantics — runtime aborts became real TypeErrors (Construct, Reflect, Object.*), unresolvable references throw ReferenceError, and strict mode is now threaded through the compiler into global/member/delete stores.
  • Deep pre-existing bugs flushed — setter-only accessors were invisible (FromPropertyDescriptor emitted set only if (has_getter)), new Array(n) elements read back as 0, array holes swallowed the prototype chain, destructuring swallowed iterator throws, "use strict" was lost in any function containing declarations (the hoister moves them above the directive), and symbol keys broke in/SameValue/Map/Set.
  • Missing surface — Date got a full spec rewrite (it had three methods), plus BigInt64/Uint64Array, TypedArray methods + constructor protocols, Array/Set modern methods, Iterator helpers, SharedArrayBuffer + Atomics, DisposableStack/WeakRef/FinalizationRegistry, AggregateError, the async-generator prototype chain, and a partial $262 host object in the runner.

What's left (recorded in expectations as carve-outs): Temporal (~486 tests, the single biggest future lever), eval/new Function, $262.createRealm, dynamic import, and mapped-arguments aliasing.

toshok and others added 23 commits August 1, 2026 13:24
…e work

The decision: do the new language features first, then promote
--types into the self-hosted compiler.  maam-plan's self-hosting
strategy gains the 2026-07-31 addendum with the dist audit that
motivates the ordering (zero external requires — the analysis is
pure; the ES2022 output's unparseable residue is exactly the
language-P3 payoff list plus six stdlib call sites, so language-first
turns the phase into build config + an import seam).  maam-P5 defines
the work items and the stage0-vs-stage1 differential gate; plans.md
grows P11 referencing it, ordered after P8, interleaving freely with
P9.5/P10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test/test262/run-test262.mjs: host-side probe runner over a curated
test262 slice (all of language/, 3-per-leaf-dir built-ins sample,
harness/) against a staged ejs — frontmatter/includes assembly,
negative-test and async semantics, outcome classification, and a
per-feature prioritized report.

Probe of 26,820 tests @ test262 5ef1e572: 35% pass; the parser is the
quantified long pole (44% of the language area fails to parse).  New
beyond the modernization census: builtin property attributes wrong
everywhere (propertyHelper poisoned, ~2.4k tests), 589 JS-reachable
runtime aborts that should be TypeErrors, super-in-object-literal EIR
lowering error, 352 early-error gaps (regexp validation the mass).
Full analysis + language-P3 payoff ordering in
docs/language-p1-results.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All parse entry points (gather-imports parseFile, eir unit tests) now go
through lib/parser.ts.  The contract is the ESTree dialect in
lib/estree.ts; the parser behind the seam becomes swappable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Running acorn under the self-hosted compiler exposed five runtime bugs,
all pre-existing:

- RegExp: pcre16_compile failures were silently ignored, leaving a NULL
  compiled_pattern that matched anything (acorn's lineBreak regex with
  \u2028 hit this).  Now: PCRE_JAVASCRIPT_COMPAT (\uXXXX escapes), a
  loud SyntaxError on compile failure, and UTF-16 mode only under /u --
  non-u patterns match per code unit and may contain lone surrogates
  (parser identifier tables; PCRE_UTF16 rejects those outright).
- String.prototype.indexOf ignored fromIndex (acorn's block-comment
  scanner loops forever: indexOf('*/', pos) returned an earlier match
  and the cursor moved backward).  lastIndexOf same, now a bounded
  reverse search.
- utf8_to_ucs2 had no 4-byte branch: 0xF0 leads fell into the 3-byte
  case (0xF0 & 0xE0 == 0xE0), decoded garbage, and the leftover
  continuation byte truncated the rest of the string.  Astral chars in
  a file ate everything after them.  Now decodes code points and emits
  surrogate pairs.
- JSON.stringify hex escapes used hexdigits table "012356789abcdef" --
  missing the 4; every digit >= 4 was off by one (\u000b printed as
  \u000c).
- String.fromCharCode used the NUL-terminated string constructor, so
  fromCharCode(0) built a zero-length string.

With these, acorn's ES5 bundle compiled by stage1 parses the entire
srcdir+test corpus (537 files) byte-identically to acorn under node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The upstream flat ESM bundle transpiled to ES5-level syntax (regen.sh,
pinned versions) so stage1+ can self-compile it.  A generated artifact,
not a forked submodule.  Wired into //external-deps:compiler-js and the
stage0 CommonJS conversion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… gates

lib/parser.ts now defaults to acorn (ecmaVersion latest, standard
ESTree) and adapts to the compiler dialect in the seam:

- top-level parameter AssignmentPatterns hoist to the aligned defaults
  array (nested pattern defaults stay in place, matching the fork)
- TryStatement grows handlers[] / guardedHandlers
- MetaProperty meta/property flatten to raw names

Everything acorn parses but the backend cannot lower yet dies at the
seam with a located error instead of miscompiling silently (the census
'async m() {}' hazard class): async/await, for await, class fields /
private members / static blocks, object spread, optional chaining,
??/**/logical-assignment operators, BigInt literals, dynamic import(),
import.meta, catch without binding.  language-P3 removes gates as
features land.

--parser esprima keeps the pre-P2 fork reachable for bisection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
generator5 (kangax generator value-sending) un-xfailed: it passes under
acorn.  Matrix all lanes 426/20/0, test-eir 227, lowtier green, corpus
AST-identity acorn-under-stage1 == acorn-under-node (537 files).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… class fields/private, async/await

Deletes the P8.2 unsupported-syntax gates in payoff order:

- ** / **= as a real generic binop (EIR `exp` op, _ejs_op_exp;
  _ejs_number_exponentiate shared with Math.pow for the C-pow spec edges)
- ?? lowered natively in EIR's logical() (loose_eq-null nullish test)
- logical assignment + optional chaining: DesugarModernOps (new pass,
  runs first; arrow-IIFE desugars keep this/arguments/super lexical)
- object spread (CopyDataProperties + descriptor-preserving chunk merge
  runtime helpers) and object rest (exclusion lists, computed keys
  evaluated once); catch without a binding (adapter-synthesized param)
- class fields, private members, static blocks: DesugarClasses grows a
  private-scope stack; fields init via per-class %initFields closures
  (base: ctor top; derived: after each super()), computed keys evaluate
  at class-definition time, #names are per-name weakmaps + a brand map
  with checked C accessors (real TypeErrors, runtime-thrown for
  private-method writes so logical assignment can short-circuit past),
  static fields/blocks run in order via %initStatics
- async/await: DesugarAsyncFunctions rewrites async bodies to the
  coroutine generators (await -> yield) driven by a per-wrapper
  __ejs_asyncDrive promise stepper; async methods/arrows covered;
  `for await` lowers to the async-iteration protocol with the
  async-from-sync fallback; Symbol.asyncIterator added.  async
  generator functions stay gated.

Eight pre-existing bugs flushed out and fixed: super.other() dispatched
via the enclosing method's key (miscompile as old as classes),
DesugarGeneratorFunctions popped its %gen mapping for every nested
function, Promise.all's resolve-element had been an #if-notyet stub
since 2015 (shared remaining-count record now), array/string
OwnPropertyKeys/GetOwnProperty were map-only (elements invisible to
Reflect-style consumers; holes reported as own; index descriptors
lacked attributes), array DefineOwnProperty clobbered elements/length
on attribute-only defines (freeze emptied template callsites),
ToEJSBool aborted on symbols and called NaN truthy, String(symbol)
aborted, and class methods/accessors were defined
non-writable/non-configurable.

test262 probe runner: shim print() for async tests (doneprintHandle
reports through it; echojs has no print global).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
12 files, node-generated expected-outs through the value harness:
exponentiation1, nullish1, optchain1, logical-assign1, object-spread1,
object-rest1, catch-binding1, class-fields1, class-private1,
class-static1, async1, for-await1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ticks

Matrix 438/20/0 at stage0-3 + shapes-off, test-eir 227, lowtier OK.
test262 area probes: class elements 26%->71%, async functions 7-14%->
76-82%, optional chaining 13->25 of 38, for-await 387/1234 (residue is
the async-generator gate).  Numbers and follow-ons in
docs/language-p3-results.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comments describe the code, not the plan history: the gc-P*/runtime-P*/
compiler-P*/sinking-P*/shapes-P*/language-P* call-outs (including the
ones the language work just added on its bug fixes) reword to what the
comment was actually saying, or drop where the phase id was the whole
parenthetical.  Also covers two test labels, the xfail reason strings,
and the --parser help text.  Phase ids remain where they belong: the
plan and results docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-through on the phase-id sweep: "used to", "the old X",
"pre-existing", "found by/found the hard way", "long-standing", "since
2015" and friends go stale the same way phase ids do.  Comments now
state the invariant or rationale in the present tense ("writing
n.body.body here would clobber ...", "an unconditional shift would let
..."), and pure war-story parentheticals are dropped.  Present-tense
uses stay: algorithmic state ("no longer reachable from entry"), GC
generation terms ("the old gen"), the live shape census, and spec
quotations.  The parser-seam gate examples update to the currently
gated set (async generators, BigInt, dynamic import()).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test/test262/lane.sh runs a fixed curated selection (every 6th
language test, 2 per built-ins leaf dir, all of harness — 6,121
tests) against the suite SHA pinned in suite.sha, checked against
the checked-in expectations.txt.  Regressions (expected-pass test
failing) and stale expectations (expected-fail test passing) both
fail the run, so the file only shrinks — a conformance ratchet.
Runs in the macOS bootstrap job after the stage ladder.

Runner: --stride-language proportional sampling, --expectations /
--update-expectations (membership-checked; `skip` lines mark
environment-sensitive tests; harness-error is never baselined).

Baseline at suite b363f29d: 2,584/5,977 pass (43%), 3,393 expected
failures, verified deterministic across two full runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Async generators (async function*) land on the coroutine generators:
the body becomes a sync generator speaking a marker protocol (awaits
and yields become marked yields), driven by a per-wrapper
__ejs_asyncGenDrive that queues next/throw/return requests and settles
each with a promised iterator result; yield* delegates through a sync
relay generator whose marked yields pass through untouched.  Parser
gate deleted.

Pre-existing bugs fixed along the way:
- yield* was statement-position-only and dropped both the delegated
  return value and sent-value forwarding; now a delegation-helper call
  (valid in any expression position — the coroutine stack lets the
  helper yield from a nested frame) with IteratorClose on abrupt exit.
- for-in over objects with private fields aborted in
  _ejs_primstring_flatten: the weak-collection inverted-rep slot is
  symbol-keyed and key collection flattened it as a string.  for-in now
  skips non-string keys; the slot is non-enumerable (it also leaked
  into object spread).
- Function .length did not exist on any function; the parser records
  spec length before desugars rewrite params, make_closure carries it,
  compiled closures define it via _ejs_function_new_closure.  (Builtin
  lengths are a separate sweep.)
- Function .name: NamedEvaluation (declarators, assignments,
  properties, defaults), class methods ("A:m" internal id -> "m"),
  private methods ("#m") via ejs_display_name.
- Destructuring gaps: nested patterns under rest, member-expression
  targets, rest-pattern formals, elision in assignment position
  (emitted assignments to undeclared temps).
- globalThis added; hasOwnProperty does ToObject(this) instead of
  aborting on undefined receivers.
- Uncaught exceptions print ToString of the thrown value (the throw
  path stashes it for the terminate handler); previously blank.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
generator26.js pins value-position yield*, sent-value forwarding, the
delegated return value, nested delegation, and inner-iterator close on
gen.return(); async-generator1.js pins the async-generator protocol
(awaits, delegation over async+sync sources, queued nexts, throw/
return, class/object methods, self-asyncIterator).  generator15/16
drop their xfail — yield* iterator closing works now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Results doc + plan ticks; test/test262/expectations.txt regenerated
against the fixed compiler: 3,393 -> 2,781 expected failures (612 tests
flipped to passing, zero newly failing), lane pass rate 43% -> 53%.
Headline areas: class/elements 71% -> 90/91%, for-await-of 31% -> 90%,
async-generator 0 (gated) -> 63%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The buy in the BigInt buy-vs-build (docs/bigint-plan.md): V8's
src/bigint is a JS engine's BigInt core deliberately factored for
reuse — its DEPS file forbids depending on anything outside the
directory, internals include only std headers, and the caller-
preallocated RWDigits convention lets our GC cells own their digits
inline.  BSD-licensed; extraction is a plain file copy
(external-deps/v8-bigint/regen.sh pins the commit).  Built as a C++20
static library with the advanced algorithms (Toom-Cook/FFT
multiplication, Barrett division) enabled.

Alternatives surveyed in the plan doc: libtommath (the Unlicense C
fallback), libbf (QuickJS moved off it for BigInt), imath, GMP
(LGPL-blocked), and building our own (division/radix correctness risk
argued against).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The BigInt primitive over the vendored v8-bigint library:

- New EJSVAL_TYPE_BIGINT tag (0x09); OBJECT moves to 0x0A — it must
  stay the maximum tag (the 64-bit IS_OBJECT is a >= compare on the
  shifted tag; the emitter's mirrored isObject constant updated for
  both word sizes).  EJSBigInt cells are GC leaves (EJS_SCAN_TYPE_
  BIGINT): sign + inline little-endian digits, relocatable, no
  finalizer, no satellite allocation — results are computed directly
  into freshly allocated cells via the library's caller-preallocates
  convention (runtime/ejs-bigint.cpp, the echo-dtoa pattern, merged
  into libecho.a).

- Full ES2020 operator semantics in ejs-ops.c: ToNumeric split on
  every arithmetic/bitwise op (mixing BigInt with anything else is a
  TypeError), mixed-type relationals and loose equality (numbers,
  strings via StringToBigInt), strict-eq/SameValue by value, typeof
  "bigint", ToString/ToEJSBool/ToObject(wrapper), unary + and >>>
  reject bigints, Number(bigint) converts (the one ToNumber caller
  that does), JSON.stringify TypeError, console prints 123n.

- BigInt()/asIntN/asUintN (ToIndex semantics)/prototype
  {toString(radix), toLocaleString, valueOf, @@toStringTag}.

- Literals: the parser adapter transmutes Literal{bigint} nodes into
  %bigintFromLiteral("<digits>") intrinsic calls (acorn's .value is
  null under the self-hosted parse; separators stripped at runtime).
  Parser gate deleted.

- Optimizer soundness: the "result is always Number" assumptions
  (cleanup.ts NUMBER_RESULT, optimize-guards) are false under BigInt.
  Replaced with the one-proven-number-operand rule — mixing throws,
  so an op that completes with a number operand produced a number —
  keeping every existing lattice fold and guard elision (the hypot2
  eir tests now pin the slow-path add staying generic: it may
  legally add two bigints).

- ++/-- lower as to_numeric (new op; bigints pass through, folds
  mirror unary_plus) + add/sub carrying an `update` imm routed to
  _ejs_op_add_update/_ejs_op_sub_update in generic emission — BigInt
  increments stay in-type while typed paths see the ordinary ops.

Gates: full matrix green (test-eir, lowtier, stage0-3 incl. the
stage2/3 fixed point and shapes-off; the compiler self-compiles under
the renumbered tags); test/bigint{1,2}.js byte-identical to node.
test262 built-ins/BigInt 77% (residue: builtin length/name attribute
gap, isConstructor-on-builtins — both global pre-existing — and $262
realm tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test/bigint{1,2}.js pin the whole surface against node byte-for-byte:
literals in every radix (+separators), arithmetic/pow/div-mod signs
and errors, two's-complement bitwise and shifts, mixed comparisons and
loose equality, BigInt()/asIntN/asUintN edges, toString(radix),
increments, truthiness, the mixing/JSON TypeErrors, wrappers.

Lane expectations regenerated: 2,781 -> 2,735 expected failures, pass
rate 53% -> 54% (BigInt gates flipped; three compile-timeout flakes
from a concurrent build re-verified passing and excluded).  Plan ticks
note the P4.2 turn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measured 2026-07-31 by compiling maam's ES-module tsc output with
stage1: the ES2022 syntax surface is covered (all 22 modules lower
clean; the smoke reaches makeMachine), so the prereqs are module
plumbing (export * / export * as ns, .js-suffixed specifiers — the
skip-module root cause), 14 probe-verified stdlib methods, the
NUL-in-source read truncation, and the maam ⊤-operand arithmetic
soundness fix now that BigInt exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…piles

Everything the self-hosted type oracle needs from the language and
runtime, plus four pre-existing bugs the stage0-vs-stage1 differential
gate flushed.

Module system:
- `export * from` and `export * as ns from`: gather-imports records
  star nodes and expands them post-gather to concrete exports via a
  fixpoint (chains work; explicit local exports shadow star names; a
  name reaching a module through two stars is exported only when both
  resolve to the same original export, else dropped with a warning).
  Lowering copies the source module's slots (the `export {a} from`
  snapshot semantics); `export * as ns` stores the source module's
  namespace object into the slot, and member reads on it resolve at
  runtime through the export accessors.
- `.js`-suffixed relative specifiers (NodeNext output style) key the
  same module as the suffix-free form — the test262 skip-module /
  _FIXTURE.js root cause.

Standard library: Object.{entries, values, fromEntries, hasOwn},
Array.prototype.{includes, at, flat, flatMap, findLast},
String.prototype.{at, padStart, padEnd, trimStart, trimEnd,
replaceAll} — spec-shaped, correct .lengths, 14 new test files.

Gate-flushed fixes (each invisible until the identical maam analysis
ran under both hosts):
- generateEJSValueForString re-resolved the just-created literal
  global BY NAME; LLVM names are NUL-terminated C strings, so literals
  differing only past an embedded U+0000 fused into one constant.
- SameValue, SameValueZero (Map/Set membership), strict/loose string
  equality and the relational operators compared flat strings with the
  C-string ucs2_strcmp, stopping at the first NUL; new length-aware
  ucs2_strcmp_len at all seven ejs-ops.c sites.
- _ejs_string_new_utf8_len treated U+0000 as end-of-input (source
  files truncated at a raw NUL — "unterminated template") and
  advanced its byte count once per code point.
- emitEjsvalFromPtr still OR'd the pre-BigInt SHIFTED_TAG_OBJECT
  (0xFFFC…) after OBJECT moved to 0x0A (0xFFFD…): module namespace
  objects were mistagged, invisible while every member read was
  compile-time-resolved.
- Array.prototype.every returned false only for literal false; any
  other falsy callback result passed the predicate (ToBoolean now).
- String.prototype.replace dropped the tail when a match ended at
  index len-1, and `$` + backtick (before-match) substitution was
  missing.

Tests: exportall1/2, jsimport1, nulsource1 (a raw NUL byte in a string
literal), nulstring1 (NUL-embedded equality/Map/Set), array-every1,
object-values1, and the stdlib suites.  Gates: full matrix green
(test-eir, lowtier, stage0-3, shapes-off), tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--types works in the shipped compiler (plans P11.1).  The seam is ONE
static import: lib/eir/oracle.ts imports `$maam` (ambient surface in
lib/maam.d.ts, structural narrowing unchanged); the __dirname-walk lazy
require() is gone.  The self-compile resolves it with
-I maam=<tree>/external-deps/echojs-maam/dist/src/index (buck-stage.sh)
to maam's ESM build, which gather-imports compiles in; the stage0 CJS
conversion seds the specifier to the CJS build staged into the
generated tree (buck-gen-js.sh).  Both builds come from emit-only tsc
genrules over the submodule src (//external-deps:maam-esm/:maam-cjs —
staging maam's package.json matters: without its "type": "module",
tsc's NodeNext mode silently emits CommonJS).  The analysis itself
still runs only under --types; flag-off compiles pay module-load cost
only.

buck-test-types-diff.sh grows a host-vs-host mode (4th arg = stage1+
exe): every test compiled --types --types-dump by BOTH hosts; the
normalized analysis stderr AND the run stdout must match.  Lane run:
520 files, 516 identical, 0 divergent (the 4 giant esprima/typedarray
files exceed the 120s per-file cap under the self-hosted compiler;
verified identical with a longer leash).  --types-dump binding sorts
switched to code-point order (localeCompare is host collation).

maam submodule @c4942c0: the ⊤-operand soundness fix (one-proven-
number-operand rule), exact concrete bigint evaluation, visibly-
degraded async/generator modeling, bigint/async corpus, TS7 toolchain,
and an emptied ejs-known-divergences.json — the ejs lane verified all
seven 2026-07-23 entries fixed (runtime-P1) and now runs 51/51 OK.

Gates: full bootstrap matrix green, the types lane above, maam
typecheck + 274 tests + diff-harness GATE PASS, and the README's
source-checkout-only caveat deleted.  docs/maam-p5-results.md records
the phase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test262 CI lane climbs 3260->4489 of 5977 (54.5% -> 75.1%);
expectations.txt ratchets 2735 -> 1488. Repo suite grows to 466 green
(object7/object9/tostring5 un-xfailed — their behaviors are now spec).

Runtime, attributes and semantics:
- every builtin function gets own .name (configurable) and .length;
  spec arities stamped from a generated table
  (runtime/ejs-builtin-arities.h) applied at end of init
- plain install macros define non-enumerable properties; Math/Number
  constants, global undefined/NaN/Infinity, constructor .prototype all
  carry spec attributes
- builtin methods are no longer constructors (CONSTRUCTOR_KIND_NONE;
  Array keeps its self-allocating construct path via a named kind);
  bound functions construct through their target
- runtime aborts become TypeErrors: Construct on non-constructors,
  setprop on primitives, Reflect.defineProperty/deleteProperty
  (now implemented), Object.create/setPrototypeOf/defineProperties,
  Function.prototype.apply; ToObject null/undefined messages fixed
- unresolvable reads/writes throw ReferenceError; typeof keeps the
  non-throwing global load (get_global for_typeof imm)
- strict mode threaded through the compiler (FnInfo.strict, directive
  prologue scan skips hoisted decls): strict set_global /
  member stores / delete throw per PutValue
- property descriptors: stored properties are complete (absent fields
  default), FromPropertyDescriptor emits [[Set]] (was gated on
  [[Get]] — setter-only accessors were invisible)
- SameValue/SameValueZero handle symbols; `in` uses ToPropertyKey;
  IteratorWrapper propagates next/value throws and IteratorClose runs
  on normal destructuring completion; RequireObjectCoercible guards
  empty object patterns
- generator/async-generator parameter destructuring throws at call
  time (prologue hoisted out of the deferred body)
- async generators join a real %AsyncGeneratorFunction% /
  %AsyncGeneratorPrototype% / %AsyncIteratorPrototype% chain
  (%markAsyncGen at each definition)
- NamedEvaluation covers anonymous classes (no more %anonClass_N in
  .name); BigInt literal property keys name by decimal value
- toplevel `this` is the global object (script semantics)

New surface: Date rewritten to a [[DateValue]] double (full getter/
setter/toISOString/parse/UTC API — it had three methods), BigInt64/
BigUint64Array, DataView spec checks, %TypedArray%.prototype filled
out (iterators, copy methods, sort, toLocaleString, prototype getters,
constructor-from-iterable/array-like), Array toReversed/toSorted/
toSpliced/with, Set methods (union et al), Iterator + iterator
helpers, SharedArrayBuffer + single-agent Atomics, DisposableStack/
AsyncDisposableStack/SuppressedError, FinalizationRegistry, WeakRef,
AggregateError, Error.isError + cause, Map/WeakMap getOrInsert(,
Computed) + groupBy + WeakMap symbol keys, String isWellFormed/
toWellFormed, Math f16round/sumPrecise + missing constants, JSON
rawJSON/isRawJSON + real parse SyntaxErrors, arguments-object
property-map semantics (define/delete/descriptors), Uint8Clamped
correct clamping, array holes fall through to the prototype chain,
accessors on array indices, new Array(n) holes (was reading zeros).

Test harness: the runner grows a partial $262 (detachArrayBuffer via
__ejs.detachArrayBuffer over a real detached flag, gc) and publishes
$DONE on globalThis for async tests.

Carve-outs recorded in expectations: Temporal, eval/Function
constructor, $262.createRealm/evalScript/agent, dynamic import,
module-flagged tests (runner skips), mapped-arguments aliasing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
toshok and others added 2 commits August 1, 2026 15:53
… await

Programs now compile under the ECMAScript Module goal: every toplevel
is strict and toplevel `this` is undefined.  The new `--script` flag
restores script-goal semantics (sloppy toplevel unless a "use strict"
directive, `this` = globalThis).  Parsing uses the module grammar
either way — the goal is a semantics switch (CompilerOptions.script ->
analyzeToplevel strictness + mod_ctx.script for toplevel `this`).

Landed alongside, since the flip exposed them:

- top-level await: a module toplevel containing `await` (or toplevel
  for-await) becomes an async function via the existing async desugar;
  module resolution gets a promise and the runloop drives the
  continuations.  The toplevel promise's rejection routes to
  __ejs.unhandledException so a failed await exits nonzero.
- node-visitor now visits computed MethodDefinition keys (no pass saw
  them before — surfaced by `class { [await x]() {} }`).
- class bodies are strict (10.2.1): the class desugar stamps its IIFE
  ejs_strict, which every synthesized class function inherits.
- sloppy-mode `this` coercion (9.2.1.2): functions that read `this` in
  sloppy code coerce null/undefined receivers to the global object at
  entry.  Strict code — i.e. everything under the module default —
  emits nothing.
- async-generator declarations' instances now sit on the spec chain
  (driver objects are Object.create(fn.prototype)).
- process.env is mutable (node parity; its non-writable entries made
  the driver's PATH prepend a silent no-op, which strict mode turned
  into a crash).

test262 runner: unflagged tests compile with --script; flags:[module]
tests use the module default with sibling *_FIXTURE.js files staged
next to the source (kept under their original basename so
self-imports resolve); negative resolution-phase tests expect
compile-time failure; captured child output is capped so runaway
tests can't overflow the collector.  The 142 formerly-skipped module
tests now run: lane 4575/6119 (74.8% on the grown denominator; 75.5%
on the previous non-module basis), expectations regenerated at 1544.

Repo tester passes --script (its node baselines run under CJS
require, i.e. script semantics).  Suite 466 green under stage1 AND
stage2; stage3 builds — the compiler bootstraps as strict module code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The array-pattern desugar now emits wrapper.close() for the
normal-completion IteratorClose; the literal-walk fold's use
discipline rejected the two extra wrapper uses, so destructuring
over literals stopped dissolving to pure SSA (test-eir's two
destructuring fold tests, red on CI for macos-arm64/linux-arm64).

IteratorClose on a literal's exhausted array iterator is a no-op —
there is no return method — so the fold accepts exactly the
getNextValue pairs plus close pairs, folds the close call to
undefined, and removes both getters with the wrapper.

Full local matrix green: test-eir + test-stage0..3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@toshok
toshok merged commit 44bf863 into main Aug 2, 2026
3 checks passed
@toshok
toshok deleted the language-p8 branch August 2, 2026 04:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant