diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 01321beeb5..01f448b28b 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -115,6 +115,7 @@ set(SOURCES Runtime/MapPrototype.cpp Runtime/MarkedValueList.cpp Runtime/MathObject.cpp + Runtime/ModuleEnvironment.cpp Runtime/NativeFunction.cpp Runtime/NumberConstructor.cpp Runtime/NumberObject.cpp diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 715db75ff1..6c65a23ee3 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -65,6 +65,7 @@ M(JsonCircular, "Cannot stringify circular object") \ M(JsonMalformed, "Malformed JSON string") \ M(MissingRequiredProperty, "Required property {} is missing or undefined") \ + M(ModuleNoEnvironment, "Cannot find module environment for imported binding") \ M(NegativeExponent, "Exponent must be positive") \ M(NonExtensibleDefine, "Cannot define property {} on non-extensible object") \ M(NotAConstructor, "{} is not a constructor") \ diff --git a/Userland/Libraries/LibJS/Runtime/ModuleEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/ModuleEnvironment.cpp new file mode 100644 index 0000000000..e02cca0d2f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ModuleEnvironment.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022, David Tuin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS { + +// 9.1.2.6 NewModuleEnvironment ( E ), https://tc39.es/ecma262/#sec-newmoduleenvironment +ModuleEnvironment::ModuleEnvironment(Environment* outer_environment) + : DeclarativeEnvironment(outer_environment) +{ +} + +// 9.1.1.5.1 GetBindingValue ( N, S ), https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s +ThrowCompletionOr ModuleEnvironment::get_binding_value(GlobalObject& global_object, FlyString const& name, bool strict) +{ + // 1. Assert: S is true. + VERIFY(strict); + + // 2. Assert: envRec has a binding for N. + auto* indirect_binding = get_indirect_binding(name); + VERIFY(indirect_binding || !DeclarativeEnvironment::has_binding(name).is_error()); + + // 3. If the binding for N is an indirect binding, then + if (indirect_binding) { + // a. Let M and N2 be the indirection values provided when this binding for N was created. + + // b. Let targetEnv be M.[[Environment]]. + auto* target_env = indirect_binding->module->environment(); + + // c. If targetEnv is empty, throw a ReferenceError exception. + if (!target_env) + return vm().throw_completion(global_object, ErrorType::ModuleNoEnvironment); + + // d. Return ? targetEnv.GetBindingValue(N2, true). + return target_env->get_binding_value(global_object, indirect_binding->binding_name, true); + } + + // 4. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. + // 5. Return the value currently bound to N in envRec. + // Note: Step 4 & 5 are the steps performed by declarative environment GetBindingValue + return DeclarativeEnvironment::get_binding_value(global_object, name, strict); +} + +// Not defined in the spec, see comment in the header. +ThrowCompletionOr ModuleEnvironment::has_binding(FlyString const& name, Optional* out_index) const +{ + // 1. If envRec has a binding for the name that is the value of N, return true. + if (get_indirect_binding(name)) + return true; + return DeclarativeEnvironment::has_binding(name, out_index); + + // 2. Return false. +} + +// 9.1.1.5.2 DeleteBinding ( N ), https://tc39.es/ecma262/#sec-module-environment-records-deletebinding-n +ThrowCompletionOr ModuleEnvironment::delete_binding(GlobalObject&, FlyString const&) +{ + // The DeleteBinding concrete method of a module Environment Record is never used within this specification. + VERIFY_NOT_REACHED(); +} + +// 9.1.1.5.4 GetThisBinding ( ), https://tc39.es/ecma262/#sec-module-environment-records-getthisbinding +ThrowCompletionOr ModuleEnvironment::get_this_binding(GlobalObject&) const +{ + // 1. Return undefined. + return js_undefined(); +} + +// 9.1.1.5.5 CreateImportBinding ( N, M, N2 ), https://tc39.es/ecma262/#sec-createimportbinding +ThrowCompletionOr ModuleEnvironment::create_import_binding(FlyString name, Module* module, FlyString binding_name) +{ + // 1. Assert: envRec does not already have a binding for N. + VERIFY(!get_indirect_binding(name)); + // 2. Assert: When M.[[Environment]] is instantiated it will have a direct binding for N2. + // FIXME: I don't know what this means or how to check it. + + // 3. Create an immutable indirect binding in envRec for N that references M and N2 as its target binding and record that the binding is initialized. + // Note: We use the fact that the binding is in this list as it being initialized. + m_indirect_bindings.append({ move(name), + module, + move(binding_name) }); + + // 4. Return NormalCompletion(empty). + return {}; +} + +ModuleEnvironment::IndirectBinding const* ModuleEnvironment::get_indirect_binding(FlyString const& name) const +{ + auto binding_or_end = m_indirect_bindings.find_if([&](IndirectBinding const& binding) { + return binding.name == name; + }); + if (binding_or_end.is_end()) + return nullptr; + + return &(*binding_or_end); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ModuleEnvironment.h b/Userland/Libraries/LibJS/Runtime/ModuleEnvironment.h new file mode 100644 index 0000000000..67473443a8 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ModuleEnvironment.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, David Tuin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS { + +// 9.1.1.5 Module Environment Records, https://tc39.es/ecma262/#sec-module-environment-records +class ModuleEnvironment final : public DeclarativeEnvironment { + JS_ENVIRONMENT(ModuleEnvironment, DeclarativeEnvironment); + +public: + ModuleEnvironment(Environment* outer_environment); + + // Note: Module Environment Records support all of the declarative Environment Record methods listed + // in Table 18 and share the same specifications for all of those methods except for + // GetBindingValue, DeleteBinding, HasThisBinding and GetThisBinding. + // In addition, module Environment Records support the methods listed in Table 24. + virtual ThrowCompletionOr get_binding_value(GlobalObject&, FlyString const& name, bool strict) override; + virtual ThrowCompletionOr delete_binding(GlobalObject&, FlyString const& name) override; + virtual bool has_this_binding() const final { return true; } + virtual ThrowCompletionOr get_this_binding(GlobalObject&) const final; + ThrowCompletionOr create_import_binding(FlyString name, Module* module, FlyString binding_name); + + // Note: Although the spec does not explicitly say this we also have to implement HasBinding as + // the HasBinding method of Declarative Environment records states: + // "It determines if the argument identifier is one of the identifiers bound by the record" + // And this means that we have to include the indirect bindings of a Module Environment. + virtual ThrowCompletionOr has_binding(FlyString const& name, Optional* = nullptr) const override; + +private: + struct IndirectBinding { + FlyString name; + Module* module; + FlyString binding_name; + }; + IndirectBinding const* get_indirect_binding(FlyString const& name) const; + + // FIXME: Since we always access this via the name this could be a map. + Vector m_indirect_bindings; +}; + +}