mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 22:07:35 +00:00
LibJS: Implement spec-compliant OrdinaryToPrimitive
This renames Object::to_primitive() to Object::ordinary_to_primitive() for two reasons: - No confusion with Value::to_primitive() - To match the spec's name Also change existing uses of Object::to_primitive() to Value::to_primitive() when the spec uses the latter (which will still call Object::ordinary_to_primitive()). Object::to_string() has been removed as it's not needed anymore (and nothing the spec uses). This makes it possible to overwrite an object's toString and valueOf and have them provide results for anything that uses to_primitive() - e.g.: const o = { toString: undefined, valueOf: () => 42 }; Number(o) // 42, previously NaN ["foo", o].toString(); // "foo,42", previously "foo,[object Object]" ++o // 43, previously NaN etc.
This commit is contained in:
parent
e163db248d
commit
fb89c324c5
4 changed files with 50 additions and 42 deletions
|
@ -836,48 +836,30 @@ bool Object::has_own_property(const PropertyName& property_name) const
|
|||
return shape().lookup(property_name.to_string_or_symbol()).has_value();
|
||||
}
|
||||
|
||||
Value Object::to_primitive(Value::PreferredType preferred_type) const
|
||||
Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const
|
||||
{
|
||||
Value result = js_undefined();
|
||||
ASSERT(preferred_type == Value::PreferredType::String || preferred_type == Value::PreferredType::Number);
|
||||
|
||||
switch (preferred_type) {
|
||||
case Value::PreferredType::Default:
|
||||
case Value::PreferredType::Number: {
|
||||
result = value_of();
|
||||
if (result.is_object()) {
|
||||
result = to_string();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Value::PreferredType::String: {
|
||||
result = to_string();
|
||||
if (result.is_object())
|
||||
result = value_of();
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto& vm = this->vm();
|
||||
|
||||
ASSERT(!result.is_object());
|
||||
Vector<FlyString, 2> method_names;
|
||||
if (preferred_type == Value::PreferredType::String)
|
||||
method_names = { vm.names.toString, vm.names.valueOf };
|
||||
else
|
||||
method_names = { vm.names.valueOf, vm.names.toString };
|
||||
|
||||
for (auto& method_name : method_names) {
|
||||
auto method = get(method_name);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
if (method.is_function()) {
|
||||
auto result = vm.call(method.as_function(), const_cast<Object*>(this));
|
||||
if (!result.is_object())
|
||||
return result;
|
||||
}
|
||||
|
||||
Value Object::to_string() const
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
auto to_string_property = get(vm.names.toString);
|
||||
if (to_string_property.is_function()) {
|
||||
auto& to_string_function = to_string_property.as_function();
|
||||
auto to_string_result = vm.call(to_string_function, const_cast<Object*>(this));
|
||||
if (to_string_result.is_object())
|
||||
vm.throw_exception<TypeError>(global_object(), ErrorType::Convert, "object", "string");
|
||||
if (vm.exception())
|
||||
return {};
|
||||
auto* string = to_string_result.to_primitive_string(global_object());
|
||||
if (vm.exception())
|
||||
return {};
|
||||
return string;
|
||||
}
|
||||
return js_string(vm, String::formatted("[object {}]", class_name()));
|
||||
vm.throw_exception<TypeError>(global_object(), ErrorType::Convert, "object", preferred_type == Value::PreferredType::String ? "string" : "number");
|
||||
return {};
|
||||
}
|
||||
|
||||
Value Object::invoke(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments)
|
||||
|
|
|
@ -140,8 +140,7 @@ public:
|
|||
virtual bool prevent_extensions();
|
||||
|
||||
virtual Value value_of() const { return Value(const_cast<Object*>(this)); }
|
||||
virtual Value to_primitive(Value::PreferredType preferred_type = Value::PreferredType::Default) const;
|
||||
virtual Value to_string() const;
|
||||
virtual Value ordinary_to_primitive(Value::PreferredType preferred_type) const;
|
||||
|
||||
Value get_direct(size_t index) const { return m_storage[index]; }
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ String Value::to_string(GlobalObject& global_object) const
|
|||
case Type::BigInt:
|
||||
return m_value.as_bigint->big_integer().to_base10();
|
||||
case Type::Object: {
|
||||
auto primitive_value = as_object().to_primitive(PreferredType::String);
|
||||
auto primitive_value = to_primitive(PreferredType::String);
|
||||
if (global_object.vm().exception())
|
||||
return {};
|
||||
return primitive_value.to_string(global_object);
|
||||
|
@ -205,8 +205,12 @@ bool Value::to_boolean() const
|
|||
|
||||
Value Value::to_primitive(PreferredType preferred_type) const
|
||||
{
|
||||
if (is_object())
|
||||
return as_object().to_primitive(preferred_type);
|
||||
if (is_object()) {
|
||||
// FIXME: Also support @@toPrimitive
|
||||
if (preferred_type == PreferredType::Default)
|
||||
preferred_type = PreferredType::Number;
|
||||
return as_object().ordinary_to_primitive(preferred_type);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -277,7 +281,7 @@ Value Value::to_number(GlobalObject& global_object) const
|
|||
global_object.vm().throw_exception<TypeError>(global_object, ErrorType::Convert, "BigInt", "number");
|
||||
return {};
|
||||
case Type::Object: {
|
||||
auto primitive = m_value.as_object->to_primitive(PreferredType::Number);
|
||||
auto primitive = to_primitive(PreferredType::Number);
|
||||
if (global_object.vm().exception())
|
||||
return {};
|
||||
return primitive.to_number(global_object);
|
||||
|
|
23
Libraries/LibJS/Tests/ordinary-to-primitive.js
Normal file
23
Libraries/LibJS/Tests/ordinary-to-primitive.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
test("object with custom toString", () => {
|
||||
const o = { toString: () => "foo" };
|
||||
expect(o + "bar").toBe("foobar");
|
||||
expect([o, "bar"].toString()).toBe("foo,bar");
|
||||
});
|
||||
|
||||
test("object with uncallable toString and custom valueOf", () => {
|
||||
const o = { toString: undefined, valueOf: () => "foo" };
|
||||
expect(o + "bar").toBe("foobar");
|
||||
expect([o, "bar"].toString()).toBe("foo,bar");
|
||||
});
|
||||
|
||||
test("object with custom valueOf", () => {
|
||||
const o = { valueOf: () => 42 };
|
||||
expect(Number(o)).toBe(42);
|
||||
expect(o + 1).toBe(43);
|
||||
});
|
||||
|
||||
test("object with uncallable valueOf and custom toString", () => {
|
||||
const o = { valueOf: undefined, toString: () => "42" };
|
||||
expect(Number(o)).toBe(42);
|
||||
expect(o + 1).toBe("421");
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue