Comparing v4.6.4...v4.7.0 · sebastienros/jint (original) (raw)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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