diff options
-rw-r--r-- | Userland/Libraries/LibJS/Forward.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/Completion.h | 151 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/VM.h | 11 |
3 files changed, 166 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 11458da86f..236c2c03e1 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -141,6 +141,7 @@ class BoundFunction; class Cell; class CellAllocator; class ClassExpression; +class Completion; class Console; class DeclarativeEnvironment; class DeferGC; @@ -225,6 +226,9 @@ JS_ENUMERATE_TEMPORAL_OBJECTS struct TemporalDuration; }; +template<typename T> +class ThrowCompletionOr; + template<class T> class Handle; diff --git a/Userland/Libraries/LibJS/Runtime/Completion.h b/Userland/Libraries/LibJS/Runtime/Completion.h new file mode 100644 index 0000000000..e84c9870c4 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Completion.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/FlyString.h> +#include <AK/Optional.h> +#include <AK/Try.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +// Temporary helper akin to TRY(), but returning a default-constructed type (e.g. empty JS::Value) +// instead of the throw completion record. Use this as the bridge between functions that have +// already been updated to use completions and functions that haven't. +#define TRY_OR_DISCARD(expression) \ + ({ \ + auto _temporary_result = (expression); \ + if (_temporary_result.is_error()) \ + return {}; \ + _temporary_result.release_value(); \ + }) + +// 6.2.3 The Completion Record Specification Type, https://tc39.es/ecma262/#sec-completion-record-specification-type +class [[nodiscard]] Completion { +public: + enum class Type { + Normal, + Break, + Continue, + Return, + Throw, + }; + + Completion(Type type, Optional<Value> value, Optional<FlyString> target) + : m_type(type) + , m_value(move(value)) + , m_target(move(target)) + { + if (m_value.has_value()) + VERIFY(!m_value->is_empty()); + } + + // 5.2.3.1 Implicit Completion Values, https://tc39.es/ecma262/#sec-implicit-completion-values + // Not `explicit` on purpose. + Completion(Value value) + : Completion(Type::Normal, value, {}) + { + } + Completion() + : Completion(js_undefined()) + { + } + + [[nodiscard]] Type type() const { return m_type; } + + [[nodiscard]] bool has_value() const { return m_value.has_value(); } + [[nodiscard]] Value value() const { return *m_value; } + + [[nodiscard]] bool has_target() const { return m_target.has_value(); } + [[nodiscard]] FlyString const& target() const { return *m_target; } + + // These are for compatibility with the TRY() macro in AK. + [[nodiscard]] bool is_error() const { return m_type == Type::Throw; } + [[nodiscard]] Value release_value() { return m_value.release_value(); } + Completion release_error() + { + VERIFY(is_error()); + return { m_type, release_value(), move(m_target) }; + } + + // 6.2.3.4 UpdateEmpty ( completionRecord, value ), https://tc39.es/ecma262/#sec-updateempty + Completion update_empty(Value value) const + { + // 1. Assert: If completionRecord.[[Type]] is either return or throw, then completionRecord.[[Value]] is not empty. + if (m_type == Type::Return || m_type == Type::Throw) + VERIFY(m_value.has_value()); + + // 2. If completionRecord.[[Value]] is not empty, return Completion(completionRecord). + if (m_value.has_value()) + return *this; + + // 3. Return Completion { [[Type]]: completionRecord.[[Type]], [[Value]]: value, [[Target]]: completionRecord.[[Target]] }. + return { m_type, value, m_target }; + } + +private: + Type m_type { Type::Normal }; // [[Type]] + Optional<Value> m_value; // [[Value]] + Optional<FlyString> m_target; // [[Target]] +}; + +template<typename ValueType> +class [[nodiscard]] ThrowCompletionOr { +public: + ThrowCompletionOr() requires(IsSame<ValueType, Empty>) = default; + + // Not `explicit` on purpose so that `return vm.throw_completion<Error>(...);` is possible. + ThrowCompletionOr(Completion throw_completion) + : m_throw_completion(move(throw_completion)) + { + VERIFY(throw_completion.is_error()); + } + + // Not `explicit` on purpose so that `return value;` is possible. + ThrowCompletionOr(ValueType value) + : m_value(move(value)) + { + if constexpr (IsSame<ValueType, Value>) + VERIFY(!value.is_empty()); + } + + [[nodiscard]] bool is_throw_completion() const { return m_throw_completion.has_value(); } + Completion const& throw_completion() const { return *m_throw_completion; } + + [[nodiscard]] bool has_value() const requires(!IsSame<ValueType, Empty>) { return m_value.has_value(); } + [[nodiscard]] ValueType const& value() const requires(!IsSame<ValueType, Empty>) { return *m_value; } + + // These are for compatibility with the TRY() macro in AK. + [[nodiscard]] bool is_error() const { return m_throw_completion.has_value(); } + [[nodiscard]] ValueType release_value() { return m_value.release_value(); } + Completion release_error() { return m_throw_completion.release_value(); } + +private: + Optional<Completion> m_throw_completion; + Optional<ValueType> m_value; +}; + +template<> +class ThrowCompletionOr<void> : public ThrowCompletionOr<Empty> { +public: + using ThrowCompletionOr<Empty>::ThrowCompletionOr; +}; + +// 6.2.3.2 NormalCompletion ( value ), https://tc39.es/ecma262/#sec-normalcompletion +inline Completion normal_completion(Value value) +{ + return { Completion::Type::Normal, value, {} }; +} + +// 6.2.3.3 ThrowCompletion ( value ), https://tc39.es/ecma262/#sec-throwcompletion +inline Completion throw_completion(Value value) +{ + return { Completion::Type::Throw, value, {} }; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h index b0b402e943..47cf627dac 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.h +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -15,6 +15,7 @@ #include <AK/Variant.h> #include <LibJS/Heap/Heap.h> #include <LibJS/Runtime/CommonPropertyNames.h> +#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/ErrorTypes.h> #include <LibJS/Runtime/Exception.h> @@ -231,6 +232,16 @@ public: return throw_exception(global_object, T::create(global_object, String::formatted(type.message(), forward<Args>(args)...))); } + // 5.2.3.2 Throw an Exception, https://tc39.es/ecma262/#sec-throw-an-exception + template<typename T, typename... Args> + Completion throw_completion(GlobalObject& global_object, ErrorType type, Args&&... args) + { + auto* error = T::create(global_object, String::formatted(type.message(), forward<Args>(args)...)); + // NOTE: This is temporary until we remove VM::exception(). + throw_exception(global_object, error); + return JS::throw_completion(error); + } + Value construct(FunctionObject&, FunctionObject& new_target, Optional<MarkedValueList> arguments); String join_arguments(size_t start_index = 0) const; |