import { surroundingAgent, HostLoadImportedModule, HostPromiseRejectionTracker, } from '../host-defined/engine.mts'; import { IncrementModuleAsyncEvaluationCount } from '../execution-context/Agent.mts'; import { CyclicModuleRecord, SyntheticModuleRecord, ResolvedBindingRecord, AbstractModuleRecord, type ModuleRecordHostDefined, ModuleRecord, } from '../modules.mts'; import { JSStringValue, ObjectValue, Value, } from '../value.mts'; import { Q, X, NormalCompletion, ThrowCompletion, AbruptCompletion, type PlainCompletion, EnsureCompletion, } from '../completion.mjs'; import { Assert, ModuleNamespaceCreate, NewPromiseCapability, PerformPromiseThen, CreateBuiltinFunction, Call, ContinueDynamicImport, PromiseCapabilityRecord, } from './all.mts'; import { Realm, Completion, HostGetSupportedImportAttributes, ModuleRequestsEqual, ReadyForSyncExecution, type Arguments, type ImportAttributeRecord, type ModuleRequestRecord, type PlainEvaluator, type ScriptRecord, type SourceTextModuleRecord, } from '#self'; /** https://tc39.es/ecma262/#graphloadingstate-record */ export class GraphLoadingState { readonly PromiseCapability: PromiseCapabilityRecord; readonly HostDefined?: ModuleRecordHostDefined; IsLoading = true; readonly Visited = new Set(); PendingModules = 1; constructor({ PromiseCapability, HostDefined }: Pick) { this.PromiseCapability = PromiseCapability; this.HostDefined = HostDefined; } } /** https://tc39.es/ecma262/#sec-InnerModuleLoading */ export function InnerModuleLoading(state: GraphLoadingState, module: AbstractModuleRecord) { // 1. Assert: state.[[IsLoading]] is true. Assert(Boolean(state.IsLoading === true)); // this Boolean() is let step 2.d.iii not having a type error. // 2. If module is a Cyclic Module Record, module.[[Status]] is new, and state.[[Visited]] does not contain module, then if (module instanceof CyclicModuleRecord && module.Status === 'new' && !state.Visited.has(module)) { // a. Append module to state.[[Visited]]. state.Visited.add(module); // b. Let requestedModulesCount be the number of elements in module.[[RequestedModules]]. const requestedModulesCout = module.RequestedModules.length; // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount. state.PendingModules += requestedModulesCout; // d. For each ModuleRequest Record request of module.[[RequestedModules]], do for (const request of module.RequestedModules) { // i. If AllImportAttributesSupported(request.[[Attributes]]) is false, then const invalidAttributeKey = AllImportAttributesSupported(request.Attributes); if (invalidAttributeKey) { // 1. Let error be ThrowCompletion(a newly created SyntaxError object). const error = surroundingAgent.Throw('SyntaxError', 'UnsupportedImportAttribute', invalidAttributeKey); // 2. Perform ContinueModuleLoading(state, error). ContinueModuleLoading(state, error); } else { // ii. Else if module.[[LoadedModules]] contains a LoadedModuleRequest Record record such that ModuleRequestsEqual(record, request) is true, then const record = getRecordWithSpecifier(module.LoadedModules, request); if (record !== undefined) { // 1. Perform InnerModuleLoading(state, record.[[Module]]). InnerModuleLoading(state, record.Module); } else { // iii. Else, // 1. Perform HostLoadImportedModule(module, request, state.[[HostDefined]], state). HostLoadImportedModule(module, request, state.HostDefined, state); } } // iii. If state.[[IsLoading]] is false, return unused. if (state.IsLoading === false) { return; } } } // 3. Assert: state.[[PendingModulesCount]] ≥ 1. Assert(state.PendingModules >= 1); // 4. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] - 1. state.PendingModules -= 1; // 5. If state.[[PendingModulesCount]] = 0, then if (state.PendingModules === 0) { // a. Set state.[[IsLoading]] to false. state.IsLoading = false; // b. For each Cyclic Module Record loaded of state.[[Visited]], do for (const loaded of state.Visited) { // i. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked. if (loaded.Status === 'new') { loaded.Status = 'unlinked'; } } // c. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »). X(Call(state.PromiseCapability.Resolve, Value.undefined, [Value.undefined])); } // 6. Return unused. } /** https://tc39.es/ecma262/#sec-ContinueModuleLoading */ export function ContinueModuleLoading(state: GraphLoadingState, result: PlainCompletion) { // 1. If state.[[IsLoading]] is false, return unused. if (state.IsLoading === false) { return; } result = EnsureCompletion(result); // 2. If moduleCompletion is a normal completion, then if (result instanceof NormalCompletion) { // a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]). InnerModuleLoading(state, result.Value); // 3. Else, } else { // a. Set state.[[IsLoading]] to false. state.IsLoading = false; // b. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »). X(Call(state.PromiseCapability.Reject, Value.undefined, [result.Value])); } // 4. Return unused. } /** https://tc39.es/ecma262/#sec-InnerModuleLinking */ export function InnerModuleLinking(module: AbstractModuleRecord, stack: CyclicModuleRecord[], index: number): PlainCompletion { if (!(module instanceof CyclicModuleRecord)) { Q(module.Link()); return index; } if (module.Status === 'linking' || module.Status === 'linked' || module.Status === 'evaluating-async' || module.Status === 'evaluated') { return index; } Assert(module.Status === 'unlinked'); module.Status = 'linking'; const moduleIndex = index; module.DFSAncestorIndex = index; index += 1; stack.push(module); for (const required of module.RequestedModules) { const requiredModule = GetImportedModule(module, required); index = Q(InnerModuleLinking(requiredModule, stack, index)); if (requiredModule instanceof CyclicModuleRecord) { Assert(requiredModule.Status === 'linking' || requiredModule.Status === 'linked' || requiredModule.Status === 'evaluating-async' || requiredModule.Status === 'evaluated'); Assert((requiredModule.Status === 'linking') === stack.includes(requiredModule)); if (requiredModule.Status === 'linking') { module.DFSAncestorIndex = Math.min(module.DFSAncestorIndex, requiredModule.DFSAncestorIndex!); } } } Q((module as SourceTextModuleRecord).InitializeEnvironment()); Assert(stack.indexOf(module) === stack.lastIndexOf(module)); Assert(module.DFSAncestorIndex <= moduleIndex); if (module.DFSAncestorIndex === moduleIndex) { let done = false; while (done === false) { const requiredModule = stack.pop(); Assert(requiredModule instanceof CyclicModuleRecord); requiredModule.Status = 'linked'; if (requiredModule === module) { done = true; } } } return index; } /** https://tc39.es/ecma262/#sec-EvaluateModuleSync */ export function* EvaluateModuleSync(module: ModuleRecord): PlainEvaluator { // 1. Assert: If module is a Cyclic Module Record, ReadyForSyncExecution(module) is true. Assert(module instanceof CyclicModuleRecord ? ReadyForSyncExecution(module) === Value.true : true); // 2. Let promise be module.Evaluate()./ const promise = yield* module.Evaluate(); // 3. Assert: promise.[[PromiseState]] is either fulfilled or rejected. Assert(promise.PromiseState === 'fulfilled' || promise.PromiseState === 'rejected'); // 4. If promise.[[PromiseState]] is rejected, then if (promise.PromiseState === 'rejected') { // a. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle"). if (promise.PromiseIsHandled === Value.false) { HostPromiseRejectionTracker(promise, 'handle'); } // b. Set promise.[[PromiseIsHandled]] to true. promise.PromiseIsHandled = Value.true; // c. Return ThrowCompletion(promise.[[PromiseResult]]). return ThrowCompletion(promise.PromiseResult!); } // 5. Return unused. return undefined; } /** https://tc39.es/ecma262/#sec-innermoduleevaluation */ export function* InnerModuleEvaluation(module: AbstractModuleRecord, stack: CyclicModuleRecord[], index: number): PlainEvaluator { if (!(module instanceof CyclicModuleRecord)) { Q(yield* EvaluateModuleSync(module)); return NormalCompletion(index); } if (module.Status === 'evaluating-async' || module.Status === 'evaluated') { if (module.EvaluationError === undefined) { return NormalCompletion(index); } else { return module.EvaluationError; } } if (module.Status === 'evaluating') { return NormalCompletion(index); } Assert(module.Status === 'linked'); module.Status = 'evaluating'; const moduleIndex = index; module.DFSAncestorIndex = index; module.PendingAsyncDependencies = 0; module.AsyncParentModules = []; index += 1; const evaluationList: ModuleRecord[] = []; for (const request of module.RequestedModules) { const requiredModule = GetImportedModule(module, request); if (request.Phase === 'defer') { const additionalModules = GatherAsynchronousTransitiveDependencies(requiredModule); for (const additionalModule of additionalModules) { if (!evaluationList.includes(additionalModule)) { evaluationList.push(additionalModule); } } } else if (!evaluationList.includes(requiredModule)) { evaluationList.push(requiredModule); } } stack.push(module); for (const required of evaluationList!) { let requiredModule: ModuleRecord | CyclicModuleRecord = required as ModuleRecord; index = Q(yield* InnerModuleEvaluation(requiredModule, stack, index)); if (requiredModule instanceof CyclicModuleRecord) { Assert(requiredModule.Status === 'evaluating' || requiredModule.Status === 'evaluating-async' || requiredModule.Status === 'evaluated'); Assert((requiredModule.Status === 'evaluating') === stack.includes(requiredModule)); if (requiredModule.Status === 'evaluating') { module.DFSAncestorIndex = Math.min(module.DFSAncestorIndex, requiredModule.DFSAncestorIndex!); } else { requiredModule = requiredModule.CycleRoot!; Assert((requiredModule as CyclicModuleRecord).Status === 'evaluating-async' || (requiredModule as CyclicModuleRecord).Status === 'evaluated'); if ((requiredModule as CyclicModuleRecord).EvaluationError !== undefined) { return EnsureCompletion((requiredModule as CyclicModuleRecord).EvaluationError); } } if (typeof (requiredModule as CyclicModuleRecord).AsyncEvaluationOrder === 'number') { module.PendingAsyncDependencies += 1; (requiredModule as CyclicModuleRecord).AsyncParentModules.push(module); } } } if (module.PendingAsyncDependencies > 0 || module.HasTLA === Value.true) { Assert(module.AsyncEvaluationOrder === 'unset'); module.AsyncEvaluationOrder = IncrementModuleAsyncEvaluationCount(); if (module.PendingAsyncDependencies === 0) { X(yield* ExecuteAsyncModule(module)); } } else { Q(yield* module.ExecuteModule()); } Assert(stack.indexOf(module) === stack.lastIndexOf(module)); Assert(module.DFSAncestorIndex <= moduleIndex); if (module.DFSAncestorIndex === moduleIndex) { let done = false; while (done === false) { const requiredModule = stack.pop(); Assert(requiredModule instanceof CyclicModuleRecord); Assert(typeof requiredModule.AsyncEvaluationOrder === 'number' || requiredModule.AsyncEvaluationOrder === 'unset'); if (requiredModule.AsyncEvaluationOrder === 'unset') { requiredModule.Status = 'evaluated'; } else { requiredModule.Status = 'evaluating-async'; } if (requiredModule === module) { done = true; } requiredModule.CycleRoot = module; } } return index; } /** https://tc39.es/proposal-defer-import-eval/#sec-GatherAsynchronousTransitiveDependencies */ export function GatherAsynchronousTransitiveDependencies(module: ModuleRecord, seen?: Set): ModuleRecord[] { // 1. If seen is not present, set seen to a new empty List. seen ??= new Set(); // 2. Let result be a new empty List. const result: ModuleRecord[] = []; // 3. If seen contains module, return result. if (seen.has(module)) { return result; } // 4. Append module to seen. seen.add(module); // 5. If module is not a Cyclic Module Record, return result. if (!(module instanceof CyclicModuleRecord)) { return result; } // 6. If module.[[Status]] is either evaluating or evaluated, return result. if (module.Status === 'evaluating' || module.Status === 'evaluated') { return result; } // 7. If module.[[HasTLA]] is true, then if (module.HasTLA === Value.true) { // a. Append module to result. result.push(module); // b. Return result. return result; } // 8. For each ModuleRequest Record request of module.[[RequestedModules]], do for (const request of module.RequestedModules) { // a. Let requiredModule be GetImportedModule(module, request). const requiredModule = GetImportedModule(module, request); // b. Let additionalModules be GatherAsynchronousTransitiveDependencies(requiredModule, seen). const additionalModules = GatherAsynchronousTransitiveDependencies(requiredModule, seen); // c. For each Module Record m of additionalModules, do for (const m of additionalModules) { // i. If result does not contain m, append m to result. if (!result.includes(m)) { result.push(m); } } } // 9. Return result. return result; } /** https://tc39.es/ecma262/#sec-execute-async-module */ function* ExecuteAsyncModule(module: CyclicModuleRecord) { // 1. Assert: module.[[Status]] is evaluating or evaluating-async. Assert(module.Status === 'evaluating' || module.Status === 'evaluating-async'); // 2. Assert: module.[[HasTLA]] is true. Assert(module.HasTLA === Value.true); // 3. Let capability be ! NewPromiseCapability(%Promise%). const capability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); // 4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and performs the following steps when called: function* fulfilledClosure() { // a. Perform ! AsyncModuleExecutionFulfilled(module). X(yield* AsyncModuleExecutionFulfilled(module)); // b. Return undefined. return Value.undefined; } // 5. Let onFulfilled be ! CreateBuiltinFunction(fulfilledClosure, 0, "", « »). const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 0, Value(''), ['Module']); // 6. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures module and performs the following steps when called: const rejectedClosure = ([error = Value.undefined]: Arguments) => { // a. Perform ! AsyncModuleExecutionRejected(module, error). X(AsyncModuleExecutionRejected(module, error)); // b. Return undefined. return Value.undefined; }; // 7. Let onRejected be ! CreateBuiltinFunction(rejectedClosure, 0, "", « »). const onRejected = CreateBuiltinFunction(rejectedClosure, 0, Value(''), ['Module']); // 8. Perform ! PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected). X(PerformPromiseThen(capability.Promise, onFulfilled, onRejected)); // 9. Perform ! module.ExecuteModule(capability). X(yield* module.ExecuteModule(capability)); // 10. Return. return Value.undefined; } /** https://tc39.es/ecma262/#sec-gather-available-ancestors */ function GatherAvailableAncestors(module: CyclicModuleRecord, execList: CyclicModuleRecord[]) { for (const m of module.AsyncParentModules) { if (!execList.includes(m) && m.CycleRoot!.EvaluationError === undefined) { Assert(m.Status === 'evaluating-async'); Assert(m.EvaluationError === undefined); Assert(typeof m.AsyncEvaluationOrder === 'number'); Assert(m.PendingAsyncDependencies! > 0); m.PendingAsyncDependencies! -= 1; if (m.PendingAsyncDependencies === 0) { execList.push(m); if (m.HasTLA === Value.false) { GatherAvailableAncestors(m, execList); } } } } } /** https://tc39.es/ecma262/#sec-asyncmodulexecutionfulfilled */ function* AsyncModuleExecutionFulfilled(module: CyclicModuleRecord): PlainEvaluator { if (module.Status === 'evaluated') { Assert(module.EvaluationError !== undefined); return; } Assert(module.Status === 'evaluating-async'); Assert(typeof module.AsyncEvaluationOrder === 'number'); Assert(module.EvaluationError === undefined); module.AsyncEvaluationOrder = 'done'; module.Status = 'evaluated'; if (module.TopLevelCapability !== undefined) { Assert(module.CycleRoot === module); X(Call(module.TopLevelCapability.Resolve, Value.undefined, [Value.undefined])); } const execList: CyclicModuleRecord[] = []; GatherAvailableAncestors(module, execList); Assert(execList.every((m) => typeof m.AsyncEvaluationOrder === 'number' && m.PendingAsyncDependencies === 0 && m.EvaluationError === undefined)); const sortedExecList = execList.toSorted((m1, m2) => (m1.AsyncEvaluationOrder as number) - (m2.AsyncEvaluationOrder as number)); for (const m of sortedExecList) { if (m.Status === 'evaluated') { Assert(m.EvaluationError !== undefined); } else if (m.HasTLA === Value.true) { X(yield* ExecuteAsyncModule(m)); } else { const result = yield* m.ExecuteModule(); if (result instanceof AbruptCompletion) { X(AsyncModuleExecutionRejected(m, result.Value)); } else { m.AsyncEvaluationOrder = 'done'; m.Status = 'evaluated'; if (m.TopLevelCapability !== undefined) { Assert(m.CycleRoot === m); X(Call(m.TopLevelCapability.Resolve, Value.undefined, [Value.undefined])); } } } } } /** https://tc39.es/ecma262/#sec-AsyncModuleExecutionRejected */ function AsyncModuleExecutionRejected(module: CyclicModuleRecord, error: Value) { if (module.Status === 'evaluated') { Assert(module.EvaluationError !== undefined); return; } Assert(module.Status === 'evaluating-async'); Assert(typeof module.AsyncEvaluationOrder === 'number'); Assert(module.EvaluationError === undefined); module.EvaluationError = ThrowCompletion(error); module.Status = 'evaluated'; module.AsyncEvaluationOrder = 'done'; if (module.TopLevelCapability !== undefined) { Assert(module.CycleRoot === module); X(Call(module.TopLevelCapability.Reject, Value.undefined, [error])); } for (const m of module.AsyncParentModules) { AsyncModuleExecutionRejected(m, error); } } function getRecordWithSpecifier(loadedModules: CyclicModuleRecord['LoadedModules'], request: ModuleRequestRecord) { const records = loadedModules.filter((r) => ModuleRequestsEqual(r, request)); Assert(records.length <= 1); return records.length === 1 ? records[0] : undefined; } /** https://tc39.es/ecma262/#sec-GetImportedModule */ export function GetImportedModule(referrer: CyclicModuleRecord, request: ModuleRequestRecord) { const record = getRecordWithSpecifier(referrer.LoadedModules, request); Assert(record !== undefined); return record.Module; } /** https://tc39.es/ecma262/#sec-FinishLoadingImportedModule */ export function FinishLoadingImportedModule(referrer: ScriptRecord | CyclicModuleRecord | Realm, moduleRequest: ModuleRequestRecord, result: PlainCompletion, state: GraphLoadingState | PromiseCapabilityRecord) { result = EnsureCompletion(result); // 1. If result is a normal completion, then if (result.Type === 'normal') { // a. If referrer.[[LoadedModules]] contains a LoadedModuleRequest Record record such that ModuleRequestsEqual(record, moduleRequest) is true, then const record = getRecordWithSpecifier(referrer.LoadedModules, moduleRequest); if (record !== undefined) { // i. Assert: That Record's [[Module]] is result.[[Value]]. Assert(record.Module === result.Value); } else { // b. Else, // i. Append the LoadedModuleRequest Record { [[Specifier]]: moduleRequest.[[Specifier]], [[Attributes]]: moduleRequest.[[Attributes]], [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. referrer.LoadedModules.push({ Specifier: moduleRequest.Specifier, Attributes: moduleRequest.Attributes, Module: result.Value }); } } // 2. If payload is a GraphLoadingState Record, then if (state instanceof GraphLoadingState) { // a. Perform ContinueModuleLoading(payload, result). ContinueModuleLoading(state, result); // 3. Else, } else { // a. Perform ContinueDynamicImport(payload, result). ContinueDynamicImport(state, result, moduleRequest.Phase); } // 4. Return unused. } /** https://tc39.es/ecma262/#sec-AllImportAttributesSupported */ export function AllImportAttributesSupported(attributes: readonly ImportAttributeRecord[]) { // Note: This function is meant to return a boolean. Instead, we return: // - instead of *false*, the key of the unsupported attribute // - instead of *true*, undefined const supported: readonly string[] = HostGetSupportedImportAttributes(); for (const attribute of attributes) { if (!supported.includes(attribute.Key.stringValue())) { return attribute.Key; } } return undefined; } /** https://tc39.es/ecma262/#sec-getmodulenamespace */ export function GetModuleNamespace( module: AbstractModuleRecord, phase: 'defer' | 'evaluation', ): ObjectValue { // 1. Assert: If module is a Cyclic Module Record, then module.[[Status]] is not new or unlinked. if (module instanceof CyclicModuleRecord) { Assert(module.Status !== 'new' && module.Status !== 'unlinked'); } // 2. Let namespace be module.[[Namespace]]. let namespace = phase === 'defer' ? module.DeferredNamespace : module.Namespace; // 3. If namespace is empty, then if (namespace === undefined) { // a. Let exportedNames be module.GetExportedNames(). const exportedNames = module.GetExportedNames(); // b. Let unambiguousNames be a new empty List. const unambiguousNames = []; // c. For each element name of exportedNames, do for (const name of exportedNames) { // i. Let resolution be module.ResolveExport(name). const resolution = module.ResolveExport(name); // ii. If resolution is a ResolvedBinding Record, append name to unambiguousNames. if (resolution instanceof ResolvedBindingRecord) { unambiguousNames.push(name); } } // d. Set namespace to ModuleNamespaceCreate(module, unambiguousNames). namespace = ModuleNamespaceCreate(module, unambiguousNames, phase); } // 4. Return namespace. return namespace; } export function CreateSyntheticModule(exportNames: readonly JSStringValue[], evaluationSteps: (record: SyntheticModuleRecord) => PlainEvaluator | Completion, realm: Realm, hostDefined: ModuleRecordHostDefined) { // 1. Return Synthetic Module Record { // [[Realm]]: realm, // [[Environment]]: undefined, // [[Namespace]]: undefined, // [[HostDefined]]: hostDefined, // [[ExportNames]]: exportNames, // [[EvaluationSteps]]: evaluationSteps // }. return new SyntheticModuleRecord({ Realm: realm, Environment: undefined, Namespace: undefined, HostDefined: hostDefined, ExportNames: exportNames, EvaluationSteps: evaluationSteps, }); } /** https://tc39.es/ecma262/#sec-create-default-export-synthetic-module */ export function CreateDefaultExportSyntheticModule(defaultExport: Value, realm: Realm, hostDefined: ModuleRecordHostDefined) { // 1. Let closure be the a Abstract Closure with parameters (module) that captures defaultExport and performs the following steps when called: const closure = function* closure(module: SyntheticModuleRecord): PlainEvaluator { // a. Return module.SetSyntheticExport("default", defaultExport). Q(yield* module.SetSyntheticExport(Value('default'), defaultExport)); return NormalCompletion(undefined); }; // 2. Return CreateSyntheticModule(« "default" », closure, realm) return CreateSyntheticModule([Value('default')], closure, realm, hostDefined); }