1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 15:57:45 +00:00

LibJS: Implement the Error Cause proposal

Currently stage 3. https://github.com/tc39/proposal-error-cause
This commit is contained in:
Linus Groh 2021-06-11 20:40:08 +01:00
parent 8d77a3297a
commit 862ba64037
11 changed files with 130 additions and 42 deletions

View file

@ -10,15 +10,9 @@
namespace JS { namespace JS {
AggregateError* AggregateError::create(GlobalObject& global_object, String const& message, Vector<Value> const& errors) AggregateError* AggregateError::create(GlobalObject& global_object)
{ {
auto& vm = global_object.vm(); return global_object.heap().allocate<AggregateError>(global_object, *global_object.aggregate_error_prototype());
auto* error = global_object.heap().allocate<AggregateError>(global_object, *global_object.aggregate_error_prototype());
u8 attr = Attribute::Writable | Attribute::Configurable;
if (!message.is_null())
error->define_property(vm.names.message, js_string(vm, message), attr);
error->define_property(vm.names.errors, Array::create_from(global_object, errors), attr);
return error;
} }
AggregateError::AggregateError(Object& prototype) AggregateError::AggregateError(Object& prototype)

View file

@ -14,7 +14,7 @@ class AggregateError : public Object {
JS_OBJECT(Error, Object); JS_OBJECT(Error, Object);
public: public:
static AggregateError* create(GlobalObject&, String const& message, Vector<Value> const& errors); static AggregateError* create(GlobalObject&);
explicit AggregateError(Object& prototype); explicit AggregateError(Object& prototype);
virtual ~AggregateError() override = default; virtual ~AggregateError() override = default;

View file

@ -6,6 +6,7 @@
#include <LibJS/Runtime/AggregateError.h> #include <LibJS/Runtime/AggregateError.h>
#include <LibJS/Runtime/AggregateErrorConstructor.h> #include <LibJS/Runtime/AggregateErrorConstructor.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ErrorConstructor.h> #include <LibJS/Runtime/ErrorConstructor.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h> #include <LibJS/Runtime/IteratorOperations.h>
@ -34,17 +35,29 @@ Value AggregateErrorConstructor::call()
Value AggregateErrorConstructor::construct(Function&) Value AggregateErrorConstructor::construct(Function&)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
String message; // FIXME: Use OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%")
auto* aggregate_error = AggregateError::create(global_object());
u8 attr = Attribute::Writable | Attribute::Configurable;
if (!vm.argument(1).is_undefined()) { if (!vm.argument(1).is_undefined()) {
message = vm.argument(1).to_string(global_object()); auto message = vm.argument(1).to_string(global_object());
if (vm.exception()) if (vm.exception())
return {}; return {};
aggregate_error->define_property(vm.names.message, js_string(vm, message), attr);
} }
aggregate_error->install_error_cause(vm.argument(2));
if (vm.exception())
return {};
auto errors_list = iterable_to_list(global_object(), vm.argument(0)); auto errors_list = iterable_to_list(global_object(), vm.argument(0));
if (vm.exception()) if (vm.exception())
return {}; return {};
// FIXME: 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »).
return AggregateError::create(global_object(), message, errors_list); aggregate_error->define_property(vm.names.errors, Array::create_from(global_object(), errors_list), attr);
return aggregate_error;
} }
} }

View file

@ -69,6 +69,7 @@ namespace JS {
P(byteOffset) \ P(byteOffset) \
P(call) \ P(call) \
P(callee) \ P(callee) \
P(cause) \
P(cbrt) \ P(cbrt) \
P(ceil) \ P(ceil) \
P(charAt) \ P(charAt) \

View file

@ -10,14 +10,17 @@
namespace JS { namespace JS {
Error* Error::create(GlobalObject& global_object, const String& message) Error* Error::create(GlobalObject& global_object)
{
return global_object.heap().allocate<Error>(global_object, *global_object.error_prototype());
}
Error* Error::create(GlobalObject& global_object, String const& message)
{ {
auto& vm = global_object.vm(); auto& vm = global_object.vm();
auto* error = global_object.heap().allocate<Error>(global_object, *global_object.error_prototype()); auto* error = Error::create(global_object);
if (!message.is_null()) { u8 attr = Attribute::Writable | Attribute::Configurable;
u8 attr = Attribute::Writable | Attribute::Configurable; error->define_property(vm.names.message, js_string(vm, message), attr);
error->define_property(vm.names.message, js_string(vm, message), attr);
}
return error; return error;
} }
@ -26,21 +29,24 @@ Error::Error(Object& prototype)
{ {
} }
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
ClassName* ClassName::create(GlobalObject& global_object, const String& message) \ ClassName* ClassName::create(GlobalObject& global_object) \
{ \ { \
auto& vm = global_object.vm(); \ return global_object.heap().allocate<ClassName>(global_object, *global_object.snake_name##_prototype()); \
auto* error = global_object.heap().allocate<ClassName>(global_object, *global_object.snake_name##_prototype()); \ } \
if (!message.is_null()) { \ \
u8 attr = Attribute::Writable | Attribute::Configurable; \ ClassName* ClassName::create(GlobalObject& global_object, String const& message) \
error->define_property(vm.names.message, js_string(vm, message), attr); \ { \
} \ auto& vm = global_object.vm(); \
return error; \ auto* error = ClassName::create(global_object); \
} \ u8 attr = Attribute::Writable | Attribute::Configurable; \
\ error->define_property(vm.names.message, js_string(vm, message), attr); \
ClassName::ClassName(Object& prototype) \ return error; \
: Error(prototype) \ } \
{ \ \
ClassName::ClassName(Object& prototype) \
: Error(prototype) \
{ \
} }
JS_ENUMERATE_NATIVE_ERRORS JS_ENUMERATE_NATIVE_ERRORS

View file

@ -16,7 +16,8 @@ class Error : public Object {
JS_OBJECT(Error, Object); JS_OBJECT(Error, Object);
public: public:
static Error* create(GlobalObject&, const String& message = {}); static Error* create(GlobalObject&);
static Error* create(GlobalObject&, String const& message);
explicit Error(Object& prototype); explicit Error(Object& prototype);
virtual ~Error() override = default; virtual ~Error() override = default;
@ -30,7 +31,8 @@ public:
JS_OBJECT(ClassName, Error); \ JS_OBJECT(ClassName, Error); \
\ \
public: \ public: \
static ClassName* create(GlobalObject&, const String& message = {}); \ static ClassName* create(GlobalObject&); \
static ClassName* create(GlobalObject&, String const& message); \
\ \
explicit ClassName(Object& prototype); \ explicit ClassName(Object& prototype); \
virtual ~ClassName() override = default; \ virtual ~ClassName() override = default; \

View file

@ -31,13 +31,23 @@ Value ErrorConstructor::call()
Value ErrorConstructor::construct(Function&) Value ErrorConstructor::construct(Function&)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
String message; // FIXME: Use OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%")
auto* error = Error::create(global_object());
u8 attr = Attribute::Writable | Attribute::Configurable;
if (!vm.argument(0).is_undefined()) { if (!vm.argument(0).is_undefined()) {
message = vm.argument(0).to_string(global_object()); auto message = vm.argument(0).to_string(global_object());
if (vm.exception()) if (vm.exception())
return {}; return {};
error->define_property(vm.names.message, js_string(vm, message), attr);
} }
return Error::create(global_object(), message);
error->install_error_cause(vm.argument(1));
if (vm.exception())
return {};
return error;
} }
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
@ -64,13 +74,24 @@ Value ErrorConstructor::construct(Function&)
Value ConstructorName::construct(Function&) \ Value ConstructorName::construct(Function&) \
{ \ { \
auto& vm = this->vm(); \ auto& vm = this->vm(); \
String message = ""; \ /* FIXME: Use OrdinaryCreateFromConstructor( \
* FIXME: newTarget, "%NativeError.prototype%"). */ \
auto* error = ClassName::create(global_object()); \
\
u8 attr = Attribute::Writable | Attribute::Configurable; \
\
if (!vm.argument(0).is_undefined()) { \ if (!vm.argument(0).is_undefined()) { \
message = vm.argument(0).to_string(global_object()); \ auto message = vm.argument(0).to_string(global_object()); \
if (vm.exception()) \ if (vm.exception()) \
return {}; \ return {}; \
error->define_property(vm.names.message, js_string(vm, message), attr); \
} \ } \
return ClassName::create(global_object(), message); \ \
error->install_error_cause(vm.argument(1)); \
if (vm.exception()) \
return {}; \
\
return error; \
} }
JS_ENUMERATE_NATIVE_ERRORS JS_ENUMERATE_NATIVE_ERRORS

View file

@ -1015,6 +1015,21 @@ Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const
return {}; return {};
} }
// 20.5.8.1 InstallErrorCause, https://tc39.es/proposal-error-cause/#sec-errorobjects-install-error-cause
void Object::install_error_cause(Value options)
{
auto& vm = this->vm();
if (!options.is_object())
return;
auto& options_object = options.as_object();
if (!options_object.has_property(vm.names.cause))
return;
auto cause = options_object.get(vm.names.cause).value_or(js_undefined());
if (vm.exception())
return;
define_property(vm.names.cause, cause, Attribute::Writable | Attribute::Configurable);
}
Value Object::invoke_internal(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments) Value Object::invoke_internal(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments)
{ {
auto& vm = this->vm(); auto& vm = this->vm();

View file

@ -129,6 +129,8 @@ public:
IndexedProperties& indexed_properties() { return m_indexed_properties; } IndexedProperties& indexed_properties() { return m_indexed_properties; }
void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); } void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); }
void install_error_cause(Value options);
[[nodiscard]] Value invoke_internal(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments); [[nodiscard]] Value invoke_internal(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments);
template<typename... Args> template<typename... Args>

View file

@ -56,4 +56,11 @@ describe("normal behavior", () => {
}).errors }).errors
).toEqual(errors); ).toEqual(errors);
}); });
test("supports options object with cause", () => {
const cause = new Error();
const error = new AggregateError([], "test", { cause });
expect(error.hasOwnProperty("cause")).toBeTrue();
expect(error.cause).toBe(cause);
});
}); });

View file

@ -31,4 +31,31 @@ describe("normal behavior", () => {
expect(TypeError()).toBeInstanceOf(TypeError); expect(TypeError()).toBeInstanceOf(TypeError);
expect(new TypeError()).toBeInstanceOf(TypeError); expect(new TypeError()).toBeInstanceOf(TypeError);
}); });
test("supports options object with cause", () => {
const errors = [Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError];
const cause = new Error();
errors.forEach(T => {
const error = new T("test", { cause });
expect(error.hasOwnProperty("cause")).toBeTrue();
expect(error.cause).toBe(cause);
});
});
test("supports options object with cause (chained)", () => {
let error;
try {
try {
throw new Error("foo");
} catch (e) {
throw new Error("bar", { cause: e });
}
} catch (e) {
error = new Error("baz", { cause: e });
}
expect(error.message).toBe("baz");
expect(error.cause.message).toBe("bar");
expect(error.cause.cause.message).toBe("foo");
expect(error.cause.cause.cause).toBe(undefined);
});
}); });