diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index ef37f4f6b3..4bc9da4ba1 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -3485,11 +3485,6 @@ Completion ImportCall::execute(Interpreter& interpreter) const { InterpreterNodeScope node_scope { interpreter, *this }; auto& vm = interpreter.vm(); - auto& realm = *vm.current_realm(); - - // 2.1.1.1 EvaluateImportCall ( specifierExpression [ , optionsExpression ] ), https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call - // 1. Let referencingScriptOrModule be GetActiveScriptOrModule(). - auto referencing_script_or_module = vm.get_active_script_or_module(); // 2. Let specifierRef be the result of evaluating specifierExpression. // 3. Let specifier be ? GetValue(specifierRef). @@ -3506,88 +3501,7 @@ Completion ImportCall::execute(Interpreter& interpreter) const // a. Let options be undefined. // Note: options_value is undefined by default. - // 6. Let promiseCapability be ! NewPromiseCapability(%Promise%). - auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor())); - - // 7. Let specifierString be Completion(ToString(specifier)). - // 8. IfAbruptRejectPromise(specifierString, promiseCapability). - auto specifier_string = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, specifier->to_deprecated_string(vm)); - - // 9. Let assertions be a new empty List. - Vector assertions; - - // 10. If options is not undefined, then - if (!options_value.is_undefined()) { - // a. If Type(options) is not Object, - if (!options_value.is_object()) { - auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAnObject.message(), "ImportOptions"))); - // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). - MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); - - // ii. Return promiseCapability.[[Promise]]. - return Value { promise_capability->promise() }; - } - - // b. Let assertionsObj be Get(options, "assert"). - // c. IfAbruptRejectPromise(assertionsObj, promiseCapability). - auto assertion_object = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, options_value.get(vm, vm.names.assert)); - - // d. If assertionsObj is not undefined, - if (!assertion_object.is_undefined()) { - // i. If Type(assertionsObj) is not Object, - if (!assertion_object.is_object()) { - auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAnObject.message(), "ImportOptionsAssertions"))); - // 1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). - MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); - - // 2. Return promiseCapability.[[Promise]]. - return Value { promise_capability->promise() }; - } - - // ii. Let keys be EnumerableOwnPropertyNames(assertionsObj, key). - // iii. IfAbruptRejectPromise(keys, promiseCapability). - auto keys = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, assertion_object.as_object().enumerable_own_property_names(Object::PropertyKind::Key)); - - // iv. Let supportedAssertions be ! HostGetSupportedImportAssertions(). - auto supported_assertions = vm.host_get_supported_import_assertions(); - - // v. For each String key of keys, - for (auto const& key : keys) { - auto property_key = MUST(key.to_property_key(vm)); - - // 1. Let value be Get(assertionsObj, key). - // 2. IfAbruptRejectPromise(value, promiseCapability). - auto value = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, assertion_object.get(vm, property_key)); - - // 3. If Type(value) is not String, then - if (!value.is_string()) { - auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAString.message(), "Import Assertion option value"))); - // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). - MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); - - // b. Return promiseCapability.[[Promise]]. - return Value { promise_capability->promise() }; - } - - // 4. If supportedAssertions contains key, then - if (supported_assertions.contains_slow(property_key.to_string())) { - // a. Append { [[Key]]: key, [[Value]]: value } to assertions. - assertions.empend(property_key.to_string(), TRY(value.as_string().deprecated_string())); - } - } - } - // e. Sort assertions by the code point order of the [[Key]] of each element. NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in. - // Note: This is done when constructing the ModuleRequest. - } - - // 11. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Assertions]]: assertions }. - ModuleRequest request { specifier_string, assertions }; - - // 12. Perform HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability). - MUST_OR_THROW_OOM(interpreter.vm().host_import_module_dynamically(referencing_script_or_module, move(request), promise_capability)); - - // 13. Return promiseCapability.[[Promise]]. - return Value { promise_capability->promise() }; + return perform_import_call(vm, *specifier, options_value); } // 13.2.3.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-literals-runtime-semantics-evaluation diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index f202aacc54..9acb63a229 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -2025,6 +2025,7 @@ public: virtual void dump(int indent) const override; virtual Completion execute(Interpreter&) const override; + virtual Bytecode::CodeGenerationErrorOr generate_bytecode(Bytecode::Generator&) const override; private: virtual bool is_import_call() const override { return true; } diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 77dbf55537..f0794ffe38 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -2725,4 +2725,22 @@ Bytecode::CodeGenerationErrorOr OptionalChain::generate_bytecode(Bytecode: return generate_optional_chain(generator, *this, current_value_register, current_base_register); } +Bytecode::CodeGenerationErrorOr ImportCall::generate_bytecode(Bytecode::Generator& generator) const +{ + TRY(m_specifier->generate_bytecode(generator)); + auto specifier_reg = generator.allocate_register(); + generator.emit(specifier_reg); + + if (m_options) { + TRY(m_options->generate_bytecode(generator)); + } else { + generator.emit(js_undefined()); + } + auto options_reg = generator.allocate_register(); + generator.emit(options_reg); + + generator.emit(specifier_reg, options_reg); + return {}; +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 76cd9716e1..36c98cb71e 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -42,6 +42,7 @@ O(GetVariable) \ O(GreaterThan) \ O(GreaterThanEquals) \ + O(ImportCall) \ O(In) \ O(Increment) \ O(InstanceOf) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 73fb20109e..9908038310 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2023, Andreas Kling * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2021, Gunnar Beutner * @@ -244,6 +244,23 @@ ThrowCompletionOr Append::execute_impl(Bytecode::Interpreter& interpreter) return {}; } +ThrowCompletionOr ImportCall::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto specifier = interpreter.reg(m_specifier); + auto options_value = interpreter.reg(m_options); + interpreter.accumulator() = TRY(perform_import_call(vm, specifier, options_value)); + return {}; +} + +void ImportCall::replace_references_impl(Register from, Register to) +{ + if (m_specifier == from) + m_specifier = to; + if (m_options == from) + m_options = to; +} + // FIXME: Since the accumulator is a Value, we store an object there and have to convert back and forth between that an Iterator records. Not great. // Make sure to put this into the accumulator before the iterator object disappears from the stack to prevent the members from being GC'd. static Object* iterator_to_object(VM& vm, Iterator iterator) @@ -1552,4 +1569,9 @@ DeprecatedString BlockDeclarationInstantiation::to_deprecated_string_impl(Byteco return "BlockDeclarationInstantiation"sv; } +DeprecatedString ImportCall::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + return DeprecatedString::formatted("ImportCall specifier:{} options:{}"sv, m_specifier, m_options); +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 2ad9a764f7..34c1730e31 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2023, Andreas Kling * Copyright (c) 2021, Linus Groh * Copyright (c) 2021, Gunnar Beutner * @@ -351,6 +351,25 @@ private: bool m_is_spread = false; }; +class ImportCall final : public Instruction { +public: + ImportCall(Register specifier, Register options) + : Instruction(Type::ImportCall) + , m_specifier(specifier) + , m_options(options) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register); + +private: + Register m_specifier; + Register m_options; +}; + class IteratorToArray final : public Instruction { public: IteratorToArray() diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index c066396e69..123405fa33 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -1472,4 +1474,96 @@ Completion dispose_resources(VM& vm, GCPtr disposable, C return completion; } +ThrowCompletionOr perform_import_call(VM& vm, Value specifier, Value options_value) +{ + auto& realm = *vm.current_realm(); + + // 2.1.1.1 EvaluateImportCall ( specifierExpression [ , optionsExpression ] ), https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call + // 1. Let referencingScriptOrModule be GetActiveScriptOrModule(). + auto referencing_script_or_module = vm.get_active_script_or_module(); + + // 6. Let promiseCapability be ! NewPromiseCapability(%Promise%). + auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor())); + + // 7. Let specifierString be Completion(ToString(specifier)). + // 8. IfAbruptRejectPromise(specifierString, promiseCapability). + auto specifier_string = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, specifier.to_deprecated_string(vm)); + + // 9. Let assertions be a new empty List. + Vector assertions; + + // 10. If options is not undefined, then + if (!options_value.is_undefined()) { + // a. If Type(options) is not Object, + if (!options_value.is_object()) { + auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAnObject.message(), "ImportOptions"))); + // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); + + // ii. Return promiseCapability.[[Promise]]. + return Value { promise_capability->promise() }; + } + + // b. Let assertionsObj be Get(options, "assert"). + // c. IfAbruptRejectPromise(assertionsObj, promiseCapability). + auto assertion_object = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, options_value.get(vm, vm.names.assert)); + + // d. If assertionsObj is not undefined, + if (!assertion_object.is_undefined()) { + // i. If Type(assertionsObj) is not Object, + if (!assertion_object.is_object()) { + auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAnObject.message(), "ImportOptionsAssertions"))); + // 1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); + + // 2. Return promiseCapability.[[Promise]]. + return Value { promise_capability->promise() }; + } + + // ii. Let keys be EnumerableOwnPropertyNames(assertionsObj, key). + // iii. IfAbruptRejectPromise(keys, promiseCapability). + auto keys = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, assertion_object.as_object().enumerable_own_property_names(Object::PropertyKind::Key)); + + // iv. Let supportedAssertions be ! HostGetSupportedImportAssertions(). + auto supported_assertions = vm.host_get_supported_import_assertions(); + + // v. For each String key of keys, + for (auto const& key : keys) { + auto property_key = MUST(key.to_property_key(vm)); + + // 1. Let value be Get(assertionsObj, key). + // 2. IfAbruptRejectPromise(value, promiseCapability). + auto value = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, assertion_object.get(vm, property_key)); + + // 3. If Type(value) is not String, then + if (!value.is_string()) { + auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAString.message(), "Import Assertion option value"))); + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); + + // b. Return promiseCapability.[[Promise]]. + return Value { promise_capability->promise() }; + } + + // 4. If supportedAssertions contains key, then + if (supported_assertions.contains_slow(property_key.to_string())) { + // a. Append { [[Key]]: key, [[Value]]: value } to assertions. + assertions.empend(property_key.to_string(), TRY(value.as_string().deprecated_string())); + } + } + } + // e. Sort assertions by the code point order of the [[Key]] of each element. NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in. + // Note: This is done when constructing the ModuleRequest. + } + + // 11. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Assertions]]: assertions }. + ModuleRequest request { specifier_string, assertions }; + + // 12. Perform HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability). + MUST_OR_THROW_OOM(vm.host_import_module_dynamically(referencing_script_or_module, move(request), promise_capability)); + + // 13. Return promiseCapability.[[Promise]]. + return Value { promise_capability->promise() }; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h index 7301a7768a..58b73a2747 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h @@ -53,6 +53,8 @@ Completion dispose(VM& vm, Value, NonnullGCPtr method); Completion dispose_resources(VM& vm, Vector const& disposable, Completion completion); Completion dispose_resources(VM& vm, GCPtr disposable, Completion completion); +ThrowCompletionOr perform_import_call(VM&, Value specifier, Value options_value); + enum class CanonicalIndexMode { DetectNumericRoundtrip, IgnoreNumericRoundtrip,