Fix async test reporting and resolve async test262 failures by lahma · Pull Request #2359 · sebastienros/jint (original) (raw)

and others added 19 commits

March 24, 2026 22:08

@lahma @claude

Improve test262 runner to properly detect async test failures via $DONE/doneprintHandle.js markers, then fix the root causes surfaced:

Remaining async failures excluded by category for incremental fixing.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Resume execution with Return/Throw completion types instead of bypassing the generator body, fixing three interconnected issue categories:

Resolves 132 additional test262 failures (190 total with prior commit). Remaining 244 failures excluded by category for incremental fixing.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Wrap IteratorComplete and IteratorValue calls in AwaitAndYieldDelegation's onFulfilled handler with try-catch to properly reject the promise when the result object's done or value getters throw. Previously, these exceptions would propagate uncaught, breaking the generator state.

Resolves 48 additional test262 failures (238 total across all commits).

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Resolves 12 additional Array.fromAsync test262 failures (250 total).

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Clear _returnRequested for async generators (not just sync) before executing finally blocks in JintTryStatement. Without this, a yield inside finally would be treated as a return, overwriting the pending return value and preventing proper completion after finally.

This enables the PendingCompletionType/Value mechanism to correctly restore the original Return completion after the finally block yields and completes.

Resolves 8 additional test262 failures (258 total across all commits).

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Per spec, when an async generator returns, the return value must be Awaited via PromiseResolve to unwrap promise values:

This ensures .return(promise) unwraps the promise value, and .return(brokenPromise) properly rejects when PromiseResolve fails.

Resolves 6 additional test262 failures (264 total across all commits).

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Previously, await inside async generator bodies fell through to the blocking UnwrapIfPromise path because JintAwaitExpression only checked for AsyncFunctionInstance (null for async generators). This caused deadlocks on deferred promises and wrong microtask tick ordering.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

… and un-exclude passing tests

Array.fromAsync: don't await async iterator values when no mapfn (spec 3.j.ii.8), use ulong for length overflow check, wrap SetLength in try-catch to reject promise. Async generators: distinguish return; (no await) from return expr; (await per 13.10.1), cache PromiseResolve from yield return path to avoid double thenable "then" access. Un-exclude 14 previously over-excluded tests that were already passing.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

For-await-of: add PromiseResolve call per spec Await step 1 (13.7.5.13) to make Promise.constructor lookups observable. Fixes 6 ticks-with tests.

AsyncGeneratorYield: use PromiseResolve instead of CreateResolvedPromise to avoid creating an extra thenable job when yielding Promise values. This fixes microtask interleaving between for-await-of and Promise.then.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Per spec Dispose step 3.a, even when method is undefined (null/undefined resource), async-dispose must Await(result). This introduces a microtask tick that suspends the async function.

DisposeCapability signals NeedsAsyncTick when it encounters an async-dispose resource with no method. JintBlockStatement detects this after block exit and creates a resolved promise to suspend the async function, ensuring statements after the block run in a new microtask.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Per spec AsyncGeneratorUnwrapYieldResumption step 2, the resumption value must be Awaited before yield* delegation continues. Additionally, when GetMethod("return") returns undefined, spec step 25.iii.1 requires a second Await on the received value.

Split the yield* Return branch into async phases: Phase A: Await(resumptionValue) — makes "then" getter observable before "return" Phase B: GetMethod("return") lookup, then second Await if return is undefined

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma

@lahma @claude

Per spec IncrementModuleAsyncEvaluationCount, the [[ModuleAsyncEvaluationCount]] is a field of the Agent Record, not local to each Evaluate() call. When dynamic import() triggers a new Evaluate() call, the counter must persist to maintain correct relative ordering between pending async modules.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

…sues

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

…c generator yield

Two fixes:

  1. import.meta now uses OrdinaryObjectCreate(null) per spec instead of Object.Construct(), which gave it Object.prototype. This caused import(import.meta) to reject with a string instead of TypeError.

  2. JintMemberExpression.GetValue fast path now checks IsSuspended() after evaluating the object expression. Without this, an await suspension inside a chained expression like (await x).value.x would fall through to the slow path, re-evaluating the await and preventing the statement list _index from being saved correctly. This caused side-effectful statements (like generator .next() calls) to re-execute on every async function resume, corrupting generator state.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma @claude

Two fixes to enable all 60 previously excluded Atomics.waitAsync tests:

  1. Agent worker event loop draining: worker threads now drain the event loop after engine.Execute() until agent.leaving() is called, allowing async continuations from await Atomics.waitAsync() to be processed.

  2. For-of loop environment corruption on async resume: when a for-of body contains let declarations and await, the saved execution context captured a block-scoped environment. On resume, BodyEvaluation captured this as oldEnv instead of the function scope, corrupting the environment chain for subsequent iterations. Fixed by saving/restoring oldEnv in ForOfSuspendData.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@lahma lahma deleted the analyze-async branch

March 24, 2026 21:39

This was referenced

Mar 27, 2026

This was referenced

Apr 1, 2026

This was referenced

Jun 8, 2026

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters

[ Show hidden characters]({{ revealButtonHref }})