Skip to content

fix(compiler): regex/super/dynamic-import in async & generator bodies (#1105)#1174

Merged
nickna merged 1 commit into
mainfrom
worktree-fix-1105
Jun 29, 2026
Merged

fix(compiler): regex/super/dynamic-import in async & generator bodies (#1105)#1174
nickna merged 1 commit into
mainfrom
worktree-fix-1105

Conversation

@nickna

@nickna nickna commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Closes #1105.

Problem (reproduced firsthand)

A regex literal, super.x, or import() inside an async function / function* body silently compiled to null in compiled mode — wrong result at runtime, no error:

async function asyncRegex() { const re = /ab+c/; return re.test("abbbc"); }
function*    genRegex()   { const re = /xy+z/; yield re.test("xyyyz"); }

Interpreted: true / true. Compiled (before): false / false.

Root cause

The four state-machine emitters (async / async-arrow / generator / async-generator) derive from StatementEmitterBase, not ILEmitter, so they inherited a base EmitRegexLiteral that pushed Ldnull. The generator emitters additionally overrode EmitSuper/EmitDynamicImport to push null. The real implementations existed only in ILEmitter. The "base default pushes null, override only in ILEmitter" pattern guaranteed every such expression kind silently miscompiled in a state-machine body.

Fix

  • Regex — ported the full hoist-aware $RegExp emission from ILEmitter into the shared ExpressionEmitterBase.EmitRegexLiteral. The per-site hoist fields already hang off the shared EmittedRuntime, so it needs no sync-only state. Removed the now-redundant ILEmitter override. Regex now compiles identically in functions, arrows, and all four state machines.
  • super — removed the silent-null EmitSuper overrides in GeneratorMoveNextEmitter and AsyncGeneratorMoveNextEmitter. The base default (load hoisted thisGetSuperMethod, which resolves via the instance's runtime base type) works in the state machine. Verified super.x in generators and async generators now matches interpreted output and the existing async-function behavior.
  • dynamic import — removed the silent-null EmitDynamicImport override in GeneratorMoveNextEmitter. The base default routes through the module registry, behaving identically to async functions. (The remaining "module not pre-compiled" error is an orthogonal compiled-mode bundling limitation that affects async functions the same way — not state-machine-specific, so no super/import follow-up is needed.)
  • Tests — updated the EmitterSyncTests allowlist (removed the three stale override entries) and added 8 DifferentialParityTests snippets pinning regex/super across the four bodies (each snippet runs through both interpreter and compiler and asserts identical output).

Note: contrary to the issue's "make the base virtuals throw a CompileException + file follow-ups" suggestion, super actually works through the base default in state machines — so making them work beat failing loud, and no feature-gap follow-up is required.

Verification

  • Full xUnit suite: 14519 passed / 0 failed / 16 skipped (skips are pre-existing network/package smoke tests).
  • EmitterSyncTests (2) + DifferentialParityTests (91, incl. 8 new) green.
  • All manual repros show interp == compiled for regex/super in every state-machine body.
  • The Test262 CompiledBaseline test host-crashes here (Internal CLR error 0x80131506) under both parallel and SHARPTS_TEST262_WORKERS=1 — confirmed pre-existing by stashing to clean main (same crash), so unrelated to this change. The committed subset's RegExp folder is top-level regex = byte-identical IL.

…#1105)

The four state-machine emitters derive from StatementEmitterBase, not
ILEmitter, so a regex literal compiled to `null` in any async/generator
body (the base EmitRegexLiteral pushed Ldnull), and the generator emitters
additionally overrode EmitSuper/EmitDynamicImport to push `null`. Result:
`/re/`, `super.x`, and `import()` inside an `async function`/`function*`
silently evaluated to null at runtime — wrong result, no error.

- Port the hoist-aware $RegExp emission from ILEmitter into the shared
  ExpressionEmitterBase (the per-site hoist fields already hang off the
  shared EmittedRuntime, so it needs no sync-only state). Regex now
  compiles identically in functions, arrows, and all four state machines.
  Drop the now-redundant ILEmitter override.
- Remove the silent-null EmitSuper overrides in GeneratorMoveNextEmitter
  and AsyncGeneratorMoveNextEmitter; the base default (hoisted `this` ->
  GetSuperMethod) works in the state machine. `super.x` now resolves in
  generators and async generators, matching async functions.
- Remove the silent-null EmitDynamicImport override in
  GeneratorMoveNextEmitter; the base default routes through the module
  registry, behaving identically to async functions (the remaining
  "module not pre-compiled" bundling limitation is orthogonal and not
  state-machine-specific).
- Update EmitterSyncTests allowlist (the removed overrides) and add 8
  differential-parity snippets pinning regex/super across the four bodies.

Suite 14519/0, EmitterSyncTests + DifferentialParityTests green; verified
interp==compiled for regex/super in every state-machine body.
@nickna nickna merged commit 68be359 into main Jun 29, 2026
2 checks passed
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.

Regex / super / dynamic import silently compile to null in async & generator bodies

1 participant