import { type GCMarker, surroundingAgent } from './host-defined/engine.mts'; import { JSStringValue, Value, type Arguments, } from './value.mts'; import { kAsyncContext } from './utils/internal.mts'; import { callable, OutOfRange } from './utils/language.mts'; import type { Evaluator, ValueEvaluator } from './evaluator.mts'; import { Assert, CreateBuiltinFunction, PerformPromiseThen, PromiseCapabilityRecord, PromiseResolve, RunSuspendedContext, type IteratorRecord, } from '#self'; import { skipDebugger } from '#self'; let createNormalCompletion: (init: NormalCompletionInit) => NormalCompletionImpl; let createBreakCompletion: (init: BreakCompletionInit) => BreakCompletion; let createContinueCompletion: (init: ContinueCompletionInit) => ContinueCompletion; let createReturnCompletion: (init: ReturnCompletionInit) => ReturnCompletion; let createThrowCompletion: (init: ThrowCompletionInit) => ThrowCompletion_; type NormalCompletionInit = Pick, 'Type' | 'Value' | 'Target'>; type BreakCompletionInit = Pick; type ContinueCompletionInit = Pick; type ReturnCompletionInit = Pick; type ThrowCompletionInit = Pick; type AbruptCompletionInit = | BreakCompletionInit | ContinueCompletionInit | ReturnCompletionInit | ThrowCompletionInit; type CompletionInit = | NormalCompletionInit | AbruptCompletionInit; @callable((_target, _thisArg, [completionRecord]) => { // 1. Assert: completionRecord is a Completion Record. Assert(completionRecord instanceof Completion); // 2. Return completionRecord as the Completion Record of this abstract operation. return completionRecord; }) class CompletionImpl { declare readonly Type: 'normal' | 'break' | 'continue' | 'return' | 'throw'; readonly Value!: T | Value; readonly Target!: JSStringValue | undefined; constructor(init: CompletionInit) { if (new.target === CompletionImpl) { switch (init.Type) { case 'normal': return createNormalCompletion(init); case 'break': return createBreakCompletion(init) as CompletionImpl; case 'continue': return createContinueCompletion(init) as CompletionImpl; case 'return': return createReturnCompletion(init) as CompletionImpl; case 'throw': return createThrowCompletion(init) as CompletionImpl; default: throw OutOfRange.exhaustive(init); } } const { Type, Value, Target } = init; Assert(new.target.prototype.Type === Type); this.Value = Value as T; this.Target = Target; } // NON-SPEC mark(m: GCMarker) { m(this.Value); } static { Object.defineProperty(this, 'name', { value: 'Completion' }); } } /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export type Completion = | NormalCompletion | AbruptCompletion; /** * A NON-SPEC shorthand to notate "returns either a normal completion containing an ECMAScript language value or a throw completion". */ // export type ValueEvaluator = T | NormalCompletion | ThrowCompletion; export type ValueCompletion = T | NormalCompletion | ThrowCompletion; export { type ValueEvaluator } from './evaluator.mts'; /** * A NON-SPEC shorthand to notate "returns either a normal completion containing ... or a throw completion". * * If the T is an ECMAScript language value, use ExpressionCompletion. */ export type PlainCompletion = T | NormalCompletion | ThrowCompletion; export type YieldCompletion = NormalCompletion | ThrowCompletion | ReturnCompletion; /** https://tc39.es/ecma262/#sec-completion-ao */ export const Completion = CompletionImpl as { /** https://tc39.es/ecma262/#sec-completion-ao */ >(completionRecord: T): T; /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ new (completion: { Type: 'normal', Value: T, Target: undefined }): NormalCompletion; new(completion: { Type: 'break', Value: void, Target: JSStringValue | undefined }): BreakCompletion; new(completion: { Type: 'continue', Value: void, Target: JSStringValue | undefined }): ContinueCompletion; new(completion: { Type: 'return', Value: Value, Target: undefined }): ReturnCompletion; new(completion: { Type: 'throw', Value: Value, Target: undefined }): ThrowCompletion; readonly prototype: CompletionImpl; }; @callable((_target, _thisArg, [value]) => { // eslint-disable-line arrow-body-style -- Preserve algorithm steps comments // 1. Return Completion { [[Type]]: normal, [[Value]]: value, [[Target]]: empty }. return new Completion({ Type: 'normal', Value: value, Target: undefined }); }) class NormalCompletionImpl extends CompletionImpl { declare readonly Type: 'normal'; declare readonly Value: T; declare readonly Target: undefined; private constructor(init: NormalCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor super(init); } static { Object.defineProperty(this, 'name', { value: 'NormalCompletion' }); Object.defineProperty(this.prototype, 'Type', { value: 'normal' }); createNormalCompletion = (init) => new NormalCompletionImpl(init); } } /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export type NormalCompletion = NormalCompletionImpl; /** https://tc39.es/ecma262/#sec-normalcompletion */ export const NormalCompletion = NormalCompletionImpl as typeof NormalCompletionImpl & { /** https://tc39.es/ecma262/#sec-normalcompletion */ (value: T): NormalCompletion; }; /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export type AbruptCompletion = | ThrowCompletion | ReturnCompletion | BreakCompletion | ContinueCompletion; /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export const AbruptCompletion = (() => { abstract class AbruptCompletion extends CompletionImpl { declare readonly Type: 'break' | 'continue' | 'return' | 'throw'; declare readonly Value: T | Value; declare readonly Target: JSStringValue | undefined; constructor(init: AbruptCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor super(init); } static { Object.defineProperty(this, 'name', { value: 'AbruptCompletion' }); } } return AbruptCompletion; })(); /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export class BreakCompletion extends AbruptCompletion { declare readonly Type: 'break'; declare readonly Value: void; private constructor(init: BreakCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor super(init); } static { Object.defineProperty(this, 'name', { value: 'BreakCompletion' }); Object.defineProperty(this.prototype, 'Type', { value: 'break' }); createBreakCompletion = (init) => new BreakCompletion(init); } } /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export class ContinueCompletion extends AbruptCompletion { declare readonly Type: 'continue'; declare readonly Value: void; declare readonly Target: JSStringValue | undefined; private constructor(init: ContinueCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor super(init); } static { Object.defineProperty(this, 'name', { value: 'ContinueCompletion' }); Object.defineProperty(this.prototype, 'Type', { value: 'continue' }); createContinueCompletion = (init) => new ContinueCompletion(init); } } @callable((_target, _thisArg, [value]) => { Assert(value instanceof Value); // 1. Return Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. return new Completion({ Type: 'return', Value: value as Value, Target: undefined }); }) class ReturnCompletion_ extends AbruptCompletion { declare readonly Type: 'return'; declare readonly Value: Value; declare readonly Target: undefined; private constructor(init: ReturnCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor super(init); } static { Object.defineProperty(this, 'name', { value: 'ReturnCompletion' }); Object.defineProperty(this.prototype, 'Type', { value: 'return' }); createReturnCompletion = (init) => new ReturnCompletion(init); } } /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export type ReturnCompletion = ReturnCompletion_; /** https://tc39.es/ecma262/#sec-throwcompletion */ export const ReturnCompletion = ReturnCompletion_ as typeof ReturnCompletion_ & { /** https://tc39.es/ecma262/#sec-throwcompletion */ (value: T): ThrowCompletion; }; const debugging = false; @callable((_target, _thisArg, [value]) => { Assert(value instanceof Value); // 1. Return Completion { [[Type]]: throw, [[Value]]: value, [[Target]]: empty }. return new Completion({ Type: 'throw', Value: value as Value, Target: undefined }); }) class ThrowCompletion_ extends AbruptCompletion { declare readonly Type: 'throw'; declare readonly Value: T; declare readonly Target: undefined; readonly stack = debugging ? new Error() : undefined; private constructor(init: Pick, 'Type' | 'Value' | 'Target'>) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor super(init); if (debugging) { if ('stackTraceLimit' in Error) { Error.stackTraceLimit = Infinity; } } } static { Object.defineProperty(this, 'name', { value: 'ThrowCompletion' }); Object.defineProperty(this.prototype, 'Type', { value: 'throw' }); createThrowCompletion = (init) => new ThrowCompletion_(init); } } /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ export type ThrowCompletion = ThrowCompletion_; /** https://tc39.es/ecma262/#sec-throwcompletion */ export const ThrowCompletion = ThrowCompletion_ as typeof ThrowCompletion_ & { /** https://tc39.es/ecma262/#sec-throwcompletion */ (value: T): ThrowCompletion; }; /** https://tc39.es/ecma262/#sec-updateempty */ export type UpdateEmpty, U> = T extends NormalCompletion ? NormalCompletion : T extends BreakCompletion ? BreakCompletion : T extends ContinueCompletion ? ContinueCompletion : T extends AbruptCompletion ? T : T extends ReturnCompletion ? T : never; /** https://tc39.es/ecma262/#sec-updateempty */ export function UpdateEmpty, const T>(completionRecord: C, value: T): UpdateEmpty; export function UpdateEmpty, const T>(completionRecord: C, value: T) { // 1. Assert: If completionRecord.[[Type]] is either return or throw, then completionRecord.[[Value]] is not empty. Assert(!(completionRecord.Type === 'return' || completionRecord.Type === 'throw') || completionRecord.Value !== undefined); // 2. If completionRecord.[[Value]] is not empty, return Completion(completionRecord). if (completionRecord.Value !== undefined) { return Completion(completionRecord); } // 3. Return Completion { [[Type]]: completionRecord.[[Type]], [[Value]]: value, [[Target]]: completionRecord.[[Target]] }. return new CompletionImpl({ Type: completionRecord.Type, Value: value, Target: completionRecord.Target } as unknown as CompletionInit); // NOTE: unsound cast } /** https://tc39.es/ecma262/#sec-returnifabrupt */ export type Q = T extends NormalCompletion ? V : T extends AbruptCompletion ? never : T; /** * https://tc39.es/ecma262/#sec-returnifabrupt * https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ? OperationName() * * @internal */ export function Q(_completion: T): Q { /* node:coverage ignore next */ throw new TypeError('Q requires build'); } function Q_runtime(completion: T): Q { /* node:coverage ignore next 3 */ if (isEvaluator(completion)) { throw new TypeError('Forgot to yield* on the completion.'); } const c = EnsureCompletion(completion); if (c.Type === 'normal') { return c.Value as Q; } throw c; } export function isEvaluator(value: unknown): value is Evaluator { return !!(value && typeof value === 'object' && 'next' in value && typeof value.next === 'function'); } /** * https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ! OperationName() * @internal Use unwrapCompletion for external code. */ export function X(_completion: T | Evaluator): Q { /* node:coverage ignore next */ throw new TypeError('X() requires build'); } export function unwrapCompletion(completion: T | Evaluator): Q { /* node:coverage ignore next 3 */ if (isEvaluator(completion)) { completion = skipDebugger(completion); } const c = EnsureCompletion(completion); if (c instanceof NormalCompletion) { return c.Value as Q; } /* node:coverage ignore next */ throw new Assert.Error('Unexpected AbruptCompletion.', { cause: c }); } /** * https://tc39.es/ecma262/#sec-ifabruptcloseiterator * @internal */ export function IfAbruptCloseIterator(_value: T, _iteratorRecord: IteratorRecord): Q { /* node:coverage ignore next */ throw new TypeError('IfAbruptCloseIterator() requires build'); } /** * https://tc39.es/ecma262/#sec-ifabruptcloseiterators * @internal */ export function IfAbruptCloseIterators(_value: T, _iteratorRecords: Iterable): Q { /* node:coverage ignore next */ throw new TypeError('IfAbruptCloseIterators() requires build'); } /** * https://tc39.es/ecma262/#sec-ifabruptcloseasynciterator * @internal */ export function IfAbruptCloseAsyncIterator(_value: T, _iteratorRecord: IteratorRecord): Q { /* node:coverage ignore next */ throw new TypeError('IfAbruptCloseAsyncIterator() requires build'); } /** * https://tc39.es/ecma262/#sec-ifabruptrejectpromise * @internal */ export function IfAbruptRejectPromise(_value: T, _capability: PromiseCapabilityRecord): Q { /* node:coverage ignore next */ throw new TypeError('IfAbruptRejectPromise requires build'); } /** * This is a util for code that cannot use Q() or X() marco to emulate this behaviour. * * @example * import { evalQ } from '...' * evalQ((Q) => { * let val = Q(operation); * }); */ export function evalQ(callback: (q: typeof Q, x: typeof X) => Promise): Promise | ThrowCompletion> export function evalQ(callback: (q: typeof Q, x: typeof X) => T): NormalCompletion | ThrowCompletion export function evalQ(callback: (q: typeof Q, x: typeof X) => T | Promise): Promise | ThrowCompletion> | NormalCompletion | ThrowCompletion { try { const result = callback(Q_runtime, unwrapCompletion); if (result instanceof Promise) { return result.then(EnsureCompletion, (error) => { if (error instanceof ThrowCompletion) { return error; } throw error; }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return EnsureCompletion(result) as any; } catch (error) { if (error instanceof ThrowCompletion) { return error; } // a real error throw error; } } export type EnsureCompletion = EnsureCompletionWorker; // Distribute over `T`s that are `Completion`s, but don't distribute over `T`s that aren't `Completion`s type EnsureCompletionWorker = T extends Completion ? T : NormalCompletion>>; /** https://tc39.es/ecma262/#sec-implicit-normal-completion */ export function EnsureCompletion(val: Value): NormalCompletion; export function EnsureCompletion(val: T): EnsureCompletion; export function EnsureCompletion(val: T) { if (val instanceof Completion) { return val; } return NormalCompletion(val); } export function ValueOfNormalCompletion(value: NormalCompletion | T) { return value instanceof NormalCompletion ? value.Value : value; } export function* Await(value: Value): ValueEvaluator { // 1. Let asyncContext be the running execution context. const asyncContext = surroundingAgent.runningExecutionContext; // 2. Let promise be ? PromiseResolve(%Promise%, value). const promise = Q(yield* PromiseResolve(surroundingAgent.intrinsic('%Promise%'), value)); // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: const fulfilledClosure = function* fulfilledClosure([v = Value.undefined]: Arguments) { yield* RunSuspendedContext(asyncContext, NormalCompletion(v), 'await-resume'); return Value.undefined; }; // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 1, Value(''), []); // @ts-expect-error TODO(ts): CreateBuiltinFunction should return a specalized type FunctionObjectValue that has a kAsyncContext on it. onFulfilled[kAsyncContext] = asyncContext; // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: const rejectedClosure = function* rejectedClosure([reason = Value.undefined]: Arguments) { yield* RunSuspendedContext(asyncContext, ThrowCompletion(reason), 'await-resume'); return Value.undefined; }; // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). const onRejected = CreateBuiltinFunction(rejectedClosure, 1, Value(''), []); // @ts-expect-error TODO(ts): CreateBuiltinFunction should return a specalized type FunctionObjectValue that has a kAsyncContext on it. onRejected[kAsyncContext] = asyncContext; // 7. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected). X(PerformPromiseThen(promise, onFulfilled, onRejected)); // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. surroundingAgent.executionContextStack.pop(asyncContext); // 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available. const completion = yield { type: 'await' }; Assert(completion.type === 'await-resume'); // 10. Return. return completion.value; // 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext. }