LibJS: Sync additional Import Attributes spec changes

Some steps were not updated with tc39/ecma262#3057. This patch
syncs the remaining changes.
This commit is contained in:
Feng Yu 2025-09-22 16:46:22 -07:00 committed by Jelle Raaijmakers
parent f8c4043460
commit 61c36e2865
9 changed files with 120 additions and 66 deletions

View File

@ -85,32 +85,35 @@ void inner_module_loading(VM& vm, JS::GraphLoadingState& state, GC::Ref<Module>
// c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount. // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount.
state.pending_module_count += requested_modules_count; state.pending_module_count += requested_modules_count;
// d. For each String required of module.[[RequestedModules]], do auto find_record_in_loaded_modules = [&](ModuleRequest const& request) -> Optional<LoadedModuleRequest const&> {
for (auto const& required : cyclic_module->requested_modules()) { return AK::find_value(cyclic_module->loaded_modules(), [&](auto const& record) { return module_requests_equal(record, request); });
bool found_record_in_loaded_modules = false; };
// i. If module.[[LoadedModules]] contains a Record whose [[Specifier]] is required, then // d. For each ModuleRequest Record request of module.[[RequestedModules]], do
for (auto const& record : cyclic_module->loaded_modules()) { for (auto const& request : cyclic_module->requested_modules()) {
if (record.specifier == required.module_specifier) { // i. If AllImportAttributesSupported(request.[[Attributes]]) is false, then
// 1. Let record be that Record. if (!all_import_attributes_supported(vm, request.attributes)) {
// 1. Let error be ThrowCompletion(a newly created SyntaxError object).
auto error = vm.throw_completion<SyntaxError>(ErrorType::ImportAttributeUnsupported);
// 2. Perform InnerModuleLoading(state, record.[[Module]]). // 2. Perform ContinueModuleLoading(state, error).
inner_module_loading(vm, state, record.module); continue_module_loading(state, error);
found_record_in_loaded_modules = true;
break;
}
} }
// ii. Else if module.[[LoadedModules]] contains a LoadedModuleRequest Record record
// ii. Else, // such that ModuleRequestsEqual(record, request) is true, then
if (!found_record_in_loaded_modules) { else if (auto record = find_record_in_loaded_modules(request); record.has_value()) {
// 1. Perform HostLoadImportedModule(module, required, state.[[HostDefined]], state). // 1. Perform InnerModuleLoading(state, record.[[Module]]).
vm.host_load_imported_module(GC::Ref { *cyclic_module }, required, state.host_defined, GC::Ref<GraphLoadingState> { state }); inner_module_loading(vm, state, record->module);
}
// iii. Else,
else {
// 1. Perform HostLoadImportedModule(module, request, state.[[HostDefined]], state).
vm.host_load_imported_module(GC::Ref { *cyclic_module }, request, state.host_defined, GC::Ref<GraphLoadingState> { state });
// 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters the graph loading process through ContinueModuleLoading. // 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters the graph loading process through ContinueModuleLoading.
} }
// iii. If state.[[IsLoading]] is false, return UNUSED. // iv. If state.[[IsLoading]] is false, return UNUSED.
if (!state.is_loading) if (!state.is_loading)
return; return;
} }
@ -253,14 +256,13 @@ ThrowCompletionOr<u32> CyclicModule::inner_module_linking(VM& vm, Vector<Module*
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module: {} has requested modules: [{}]", filename(), request_module_names.string_view()); dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module: {} has requested modules: [{}]", filename(), request_module_names.string_view());
#endif #endif
// 9. For each String required of module.[[RequestedModules]], do // 9. For each ModuleRequest Record request of module.[[RequestedModules]], do
for (auto& required_string : m_requested_modules) { for (auto& request : m_requested_modules) {
ModuleRequest required { required_string };
// a. Let requiredModule be GetImportedModule(module, required). // a. Let requiredModule be GetImportedModule(module, request).
auto required_module = get_imported_module(required); auto required_module = get_imported_module(request);
// b. Set index to ? InnerModuleLinking(requiredModule, stack, index). // b. Set index to ? InnerModuleLinking(requiredModule, stack, index).
index = TRY(required_module->inner_module_linking(vm, stack, index)); index = TRY(required_module->inner_module_linking(vm, stack, index));
// c. If requiredModule is a Cyclic Module Record, then // c. If requiredModule is a Cyclic Module Record, then
@ -460,11 +462,11 @@ ThrowCompletionOr<u32> CyclicModule::inner_module_evaluation(VM& vm, Vector<Modu
// 10. Append module to stack. // 10. Append module to stack.
stack.append(this); stack.append(this);
// 11. For each String required of module.[[RequestedModules]], do // 11. For each ModuleRequest Record request of module.[[RequestedModules]], do
for (auto& required : m_requested_modules) { for (auto& request : m_requested_modules) {
// a. Let requiredModule be GetImportedModule(module, required). // a. Let requiredModule be GetImportedModule(module, request).
auto required_module = get_imported_module(required); auto required_module = get_imported_module(request);
// b. Set index to ? InnerModuleEvaluation(requiredModule, stack, index). // b. Set index to ? InnerModuleEvaluation(requiredModule, stack, index).
index = TRY(required_module->inner_module_evaluation(vm, stack, index)); index = TRY(required_module->inner_module_evaluation(vm, stack, index));
@ -810,26 +812,24 @@ void CyclicModule::async_module_execution_rejected(VM& vm, Value error)
// 9. Return unused. // 9. Return unused.
} }
// 16.2.1.7 GetImportedModule ( referrer, specifier ), https://tc39.es/ecma262/#sec-GetImportedModule // 16.2.1.9 GetImportedModule ( referrer, request ), https://tc39.es/ecma262/#sec-GetImportedModule
GC::Ref<Module> CyclicModule::get_imported_module(ModuleRequest const& request) GC::Ref<Module> CyclicModule::get_imported_module(ModuleRequest const& request)
{ {
// 1. Assert: Exactly one element of referrer.[[LoadedModules]] is a Record whose [[Specifier]] is specifier, // 1. Let records be a List consisting of each LoadedModuleRequest Record r of referrer.[[LoadedModules]]
// since LoadRequestedModules has completed successfully on referrer prior to invoking this abstract operation. // such that ModuleRequestsEqual(r, request) is true.
size_t element_with_specifier_count = 0; Vector<LoadedModuleRequest> records;
for (auto const& loaded_module : m_loaded_modules) { for (auto const& r : m_loaded_modules) {
if (loaded_module.specifier == request.module_specifier) if (module_requests_equal(r, request))
++element_with_specifier_count; records.append(r);
} }
VERIFY(element_with_specifier_count == 1);
for (auto const& loaded_module : m_loaded_modules) { // 2. Assert: records has exactly one element, since LoadRequestedModules has completed successfully
if (loaded_module.specifier == request.module_specifier) { // on referrer prior to invoking this abstract operation.
// 2. Let record be the Record in referrer.[[LoadedModules]] whose [[Specifier]] is specifier. VERIFY(records.size() == 1);
// 3. Return record.[[Module]].
return loaded_module.module; // 3. Let record be the sole element of records.
} // 4. Return record.[[Module]].
} return records.first().module;
VERIFY_NOT_REACHED();
} }
// 13.3.10.1.1 ContinueDynamicImport ( promiseCapability, moduleCompletion ), https://tc39.es/ecma262/#sec-ContinueDynamicImport // 13.3.10.1.1 ContinueDynamicImport ( promiseCapability, moduleCompletion ), https://tc39.es/ecma262/#sec-ContinueDynamicImport

View File

@ -42,8 +42,8 @@ public:
void set_status(ModuleStatus status) { m_status = status; } void set_status(ModuleStatus status) { m_status = status; }
Vector<ModuleRequest> const& requested_modules() const { return m_requested_modules; } Vector<ModuleRequest> const& requested_modules() const { return m_requested_modules; }
Vector<ModuleWithSpecifier> const& loaded_modules() const { return m_loaded_modules; } Vector<LoadedModuleRequest> const& loaded_modules() const { return m_loaded_modules; }
Vector<ModuleWithSpecifier>& loaded_modules() { return m_loaded_modules; } Vector<LoadedModuleRequest>& loaded_modules() { return m_loaded_modules; }
protected: protected:
CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules, Script::HostDefined* host_defined); CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules, Script::HostDefined* host_defined);
@ -56,7 +56,7 @@ protected:
virtual ThrowCompletionOr<void> initialize_environment(VM& vm); virtual ThrowCompletionOr<void> initialize_environment(VM& vm);
virtual ThrowCompletionOr<void> execute_module(VM& vm, GC::Ptr<PromiseCapability> capability = {}); virtual ThrowCompletionOr<void> execute_module(VM& vm, GC::Ptr<PromiseCapability> capability = {});
[[nodiscard]] GC::Ref<Module> get_imported_module(ModuleRequest const&); [[nodiscard]] GC::Ref<Module> get_imported_module(ModuleRequest const& request);
void execute_async_module(VM& vm); void execute_async_module(VM& vm);
void gather_available_ancestors(Vector<CyclicModule*>& exec_list); void gather_available_ancestors(Vector<CyclicModule*>& exec_list);
@ -68,7 +68,7 @@ protected:
Optional<u32> m_dfs_index; // [[DFSIndex]] Optional<u32> m_dfs_index; // [[DFSIndex]]
Optional<u32> m_dfs_ancestor_index; // [[DFSAncestorIndex]] Optional<u32> m_dfs_ancestor_index; // [[DFSAncestorIndex]]
Vector<ModuleRequest> m_requested_modules; // [[RequestedModules]] Vector<ModuleRequest> m_requested_modules; // [[RequestedModules]]
Vector<ModuleWithSpecifier> m_loaded_modules; // [[LoadedModules]] Vector<LoadedModuleRequest> m_loaded_modules; // [[LoadedModules]]
GC::Ptr<CyclicModule> m_cycle_root; // [[CycleRoot]] GC::Ptr<CyclicModule> m_cycle_root; // [[CycleRoot]]
bool m_has_top_level_await { false }; // [[HasTLA]] bool m_has_top_level_await { false }; // [[HasTLA]]
bool m_async_evaluation { false }; // [[AsyncEvaluation]] bool m_async_evaluation { false }; // [[AsyncEvaluation]]

View File

@ -226,7 +226,7 @@ enum class DeclarationKind;
struct AlreadyResolved; struct AlreadyResolved;
class JobCallback; class JobCallback;
struct ModuleRequest; struct ModuleRequest;
struct ModuleWithSpecifier; struct LoadedModuleRequest;
// Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
class ProxyObject; class ProxyObject;

View File

@ -95,20 +95,20 @@ void finish_loading_imported_module(ImportedModuleReferrer referrer, ModuleReque
// NOTE: Only Script and CyclicModule referrers have the [[LoadedModules]] internal slot. // NOTE: Only Script and CyclicModule referrers have the [[LoadedModules]] internal slot.
if (referrer.has<GC::Ref<Script>>() || referrer.has<GC::Ref<CyclicModule>>()) { if (referrer.has<GC::Ref<Script>>() || referrer.has<GC::Ref<CyclicModule>>()) {
auto& loaded_modules = referrer.visit( auto& loaded_modules = referrer.visit(
[](GC::Ref<JS::Realm>&) -> Vector<ModuleWithSpecifier>& { [](GC::Ref<JS::Realm>&) -> Vector<LoadedModuleRequest>& {
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
__builtin_unreachable(); __builtin_unreachable();
}, },
[](auto& script_or_module) -> Vector<ModuleWithSpecifier>& { [](auto& script_or_module) -> Vector<LoadedModuleRequest>& {
return script_or_module->loaded_modules(); return script_or_module->loaded_modules();
}); });
bool found_record = false; bool found_record = false;
// a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then // a. If referrer.[[LoadedModules]] contains a LoadedModuleRequest Record record such that ModuleRequestsEqual(record, moduleRequest) is true, then
for (auto const& record : loaded_modules) { for (auto const& record : loaded_modules) {
if (record.specifier == module_request.module_specifier) { if (module_requests_equal(record, module_request)) {
// i. Assert: That Record's [[Module]] is result.[[Value]]. // i. Assert: record.[[Module]] and result.[[Value]] are the same Module Record.
VERIFY(record.module == result.value()); VERIFY(record.module == result.value());
found_record = true; found_record = true;
} }
@ -118,9 +118,10 @@ void finish_loading_imported_module(ImportedModuleReferrer referrer, ModuleReque
if (!found_record) { if (!found_record) {
auto module = result.value(); auto module = result.value();
// i. Append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. // i. Append the LoadedModuleRequest Record { [[Specifier]]: moduleRequest.[[Specifier]], [[Attributes]]: moduleRequest.[[Attributes]], [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]].
loaded_modules.append(ModuleWithSpecifier { loaded_modules.append(LoadedModuleRequest {
.specifier = module_request.module_specifier.to_utf16_string(), .specifier = module_request.module_specifier.to_utf16_string(),
.attributes = module_request.attributes,
.module = GC::Ref<Module>(*module) }); .module = GC::Ref<Module>(*module) });
} }
} }

View File

@ -1764,7 +1764,7 @@ Completion dispose_resources(VM& vm, DisposeCapability& dispose_capability, Comp
} }
// 16.2.1.12 AllImportAttributesSupported ( attributes ), https://tc39.es/ecma262/#sec-AllImportAttributesSupported // 16.2.1.12 AllImportAttributesSupported ( attributes ), https://tc39.es/ecma262/#sec-AllImportAttributesSupported
static bool all_import_attributes_supported(VM& vm, Vector<ImportAttribute> const& attributes) bool all_import_attributes_supported(VM& vm, Vector<ImportAttribute> const& attributes)
{ {
// 1. Let supported be HostGetSupportedImportAttributes(). // 1. Let supported be HostGetSupportedImportAttributes().
auto supported = vm.host_get_supported_import_attributes(); auto supported = vm.host_get_supported_import_attributes();

View File

@ -18,6 +18,7 @@
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h> #include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/KeyedCollections.h> #include <LibJS/Runtime/KeyedCollections.h>
#include <LibJS/Runtime/ModuleRequest.h>
#include <LibJS/Runtime/PrivateEnvironment.h> #include <LibJS/Runtime/PrivateEnvironment.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
@ -69,6 +70,8 @@ ThrowCompletionOr<GC::Ptr<FunctionObject>> get_dispose_method(VM&, Value, Enviro
Completion dispose(VM&, Value, Environment::InitializeBindingHint, GC::Ptr<FunctionObject> method); Completion dispose(VM&, Value, Environment::InitializeBindingHint, GC::Ptr<FunctionObject> method);
Completion dispose_resources(VM&, DisposeCapability&, Completion); Completion dispose_resources(VM&, DisposeCapability&, Completion);
bool all_import_attributes_supported(VM& vm, Vector<ImportAttribute> const& attributes);
ThrowCompletionOr<Value> perform_import_call(VM&, Value specifier, Value options_value); ThrowCompletionOr<Value> perform_import_call(VM&, Value specifier, Value options_value);
enum class CanonicalIndexMode { enum class CanonicalIndexMode {

View File

@ -7,17 +7,13 @@
#pragma once #pragma once
#include <AK/StdLibExtras.h>
#include <AK/Utf16FlyString.h> #include <AK/Utf16FlyString.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibJS/Module.h> #include <LibJS/Module.h>
namespace JS { namespace JS {
struct ModuleWithSpecifier {
Utf16String specifier; // [[Specifier]]
GC::Ref<Module> module; // [[Module]]
};
// https://tc39.es/ecma262/#importattribute-record // https://tc39.es/ecma262/#importattribute-record
struct ImportAttribute { struct ImportAttribute {
Utf16String key; Utf16String key;
@ -26,6 +22,13 @@ struct ImportAttribute {
bool operator==(ImportAttribute const&) const = default; bool operator==(ImportAttribute const&) const = default;
}; };
// https://tc39.es/ecma262/#loadedmodulerequest-record
struct LoadedModuleRequest {
Utf16String specifier; // [[Specifier]]
Vector<ImportAttribute> attributes; // [[Attributes]]
GC::Ref<Module> module; // [[Module]]
};
// https://tc39.es/ecma262/#modulerequest-record // https://tc39.es/ecma262/#modulerequest-record
struct ModuleRequest { struct ModuleRequest {
ModuleRequest() = default; ModuleRequest() = default;
@ -48,4 +51,39 @@ struct ModuleRequest {
bool operator==(ModuleRequest const&) const = default; bool operator==(ModuleRequest const&) const = default;
}; };
inline auto const& specifier_of(ModuleRequest const& r) { return r.module_specifier; }
inline auto const& specifier_of(LoadedModuleRequest const& r) { return r.specifier; }
template<typename T>
concept ModuleRequestLike = IsOneOf<RemoveCVReference<T>, ModuleRequest, LoadedModuleRequest>;
// 16.2.1.3.1 ModuleRequestsEqual ( left, right ), https://tc39.es/ecma262/#sec-modulerequestsequal
template<ModuleRequestLike L, ModuleRequestLike R>
bool module_requests_equal(L const& left, R const& right)
{
// 1. If left.[[Specifier]] is not right.[[Specifier]], return false.
if (specifier_of(left) != specifier_of(right))
return false;
// 2. Let leftAttrs be left.[[Attributes]].
// 3. Let rightAttrs be right.[[Attributes]].
auto const& left_attrs = left.attributes;
auto const& right_attrs = right.attributes;
// 4. Let leftAttrsCount be the number of elements in leftAttrs.
// 5. Let rightAttrsCount be the number of elements in rightAttrs.
// 6. If leftAttrsCount ≠ rightAttrsCount, return false.
if (left_attrs.size() != right_attrs.size())
return false;
// 7. For each ImportAttribute Record l of leftAttrs
// a. If rightAttrs does not contain an ImportAttribute Record r such that l.[[Key]] is r.[[Key]] and l.[[Value]] is r.[[Value]], return false.
// 8. Return true.
return AK::all_of(left_attrs, [&right_attrs](auto const& l) {
return AK::any_of(right_attrs, [&l](auto const& r) {
return l.key == r.key && l.value == r.value;
});
});
}
} }

View File

@ -40,8 +40,8 @@ public:
Realm& realm() { return *m_realm; } Realm& realm() { return *m_realm; }
Program const& parse_node() const { return *m_parse_node; } Program const& parse_node() const { return *m_parse_node; }
Vector<ModuleWithSpecifier>& loaded_modules() { return m_loaded_modules; } Vector<LoadedModuleRequest>& loaded_modules() { return m_loaded_modules; }
Vector<ModuleWithSpecifier> const& loaded_modules() const { return m_loaded_modules; } Vector<LoadedModuleRequest> const& loaded_modules() const { return m_loaded_modules; }
HostDefined* host_defined() const { return m_host_defined; } HostDefined* host_defined() const { return m_host_defined; }
StringView filename() const LIFETIME_BOUND { return m_filename; } StringView filename() const LIFETIME_BOUND { return m_filename; }
@ -53,7 +53,7 @@ private:
GC::Ptr<Realm> m_realm; // [[Realm]] GC::Ptr<Realm> m_realm; // [[Realm]]
NonnullRefPtr<Program> m_parse_node; // [[ECMAScriptCode]] NonnullRefPtr<Program> m_parse_node; // [[ECMAScriptCode]]
Vector<ModuleWithSpecifier> m_loaded_modules; // [[LoadedModules]] Vector<LoadedModuleRequest> m_loaded_modules; // [[LoadedModules]]
// Needed for potential lookups of modules. // Needed for potential lookups of modules.
ByteString m_filename; ByteString m_filename;

View File

@ -196,6 +196,18 @@ int main(int argc, char** argv)
if (!g_vm) { if (!g_vm) {
g_vm = JS::VM::create(); g_vm = JS::VM::create();
g_vm->set_dynamic_imports_allowed(true); g_vm->set_dynamic_imports_allowed(true);
// Configure the test VM to support additional import attributes
// This allows tests to use import attributes beyond just "type"
Test::JS::g_vm->host_get_supported_import_attributes = []() -> Vector<Utf16String> {
return {
"type"_utf16,
"key"_utf16, // Used in modules/import-with-attributes.mjs test
"key1"_utf16, // Used in modules/basic-modules.js
"key2"_utf16, // Used in modules/import-with-attributes.mjs test
"default"_utf16, // Used in modules/import-with-attributes.mjs test
};
};
} }
Test::JS::TestRunner test_runner(test_root, common_path, print_times, print_progress, print_json, per_file); Test::JS::TestRunner test_runner(test_root, common_path, print_times, print_progress, print_json, per_file);