1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:17: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 {
AggregateError* AggregateError::create(GlobalObject& global_object, String const& message, Vector<Value> const& errors)
AggregateError* AggregateError::create(GlobalObject& global_object)
{
auto& vm = global_object.vm();
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;
return global_object.heap().allocate<AggregateError>(global_object, *global_object.aggregate_error_prototype());
}
AggregateError::AggregateError(Object& prototype)

View file

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

View file

@ -6,6 +6,7 @@
#include <LibJS/Runtime/AggregateError.h>
#include <LibJS/Runtime/AggregateErrorConstructor.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ErrorConstructor.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
@ -34,17 +35,29 @@ Value AggregateErrorConstructor::call()
Value AggregateErrorConstructor::construct(Function&)
{
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()) {
message = vm.argument(1).to_string(global_object());
auto message = vm.argument(1).to_string(global_object());
if (vm.exception())
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));
if (vm.exception())
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(call) \
P(callee) \
P(cause) \
P(cbrt) \
P(ceil) \
P(charAt) \

View file

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

View file

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

View file

@ -31,13 +31,23 @@ Value ErrorConstructor::call()
Value ErrorConstructor::construct(Function&)
{
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()) {
message = vm.argument(0).to_string(global_object());
auto message = vm.argument(0).to_string(global_object());
if (vm.exception())
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) \
@ -64,13 +74,24 @@ Value ErrorConstructor::construct(Function&)
Value ConstructorName::construct(Function&) \
{ \
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()) { \
message = vm.argument(0).to_string(global_object()); \
auto message = vm.argument(0).to_string(global_object()); \
if (vm.exception()) \
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

View file

@ -1015,6 +1015,21 @@ Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const
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)
{
auto& vm = this->vm();

View file

@ -129,6 +129,8 @@ public:
IndexedProperties& indexed_properties() { return m_indexed_properties; }
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);
template<typename... Args>

View file

@ -56,4 +56,11 @@ describe("normal behavior", () => {
}).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(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);
});
});