summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Libraries/LibJS/Runtime/Object.cpp52
-rw-r--r--Libraries/LibJS/Runtime/Object.h3
-rw-r--r--Libraries/LibJS/Runtime/Value.cpp12
-rw-r--r--Libraries/LibJS/Tests/ordinary-to-primitive.js23
4 files changed, 49 insertions, 41 deletions
diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp
index 5ed464624c..331f3b3704 100644
--- a/Libraries/LibJS/Runtime/Object.cpp
+++ b/Libraries/LibJS/Runtime/Object.cpp
@@ -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());
- return result;
-}
+ 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 };
-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");
+ for (auto& method_name : method_names) {
+ auto method = get(method_name);
if (vm.exception())
return {};
- auto* string = to_string_result.to_primitive_string(global_object());
- if (vm.exception())
- return {};
- return string;
+ if (method.is_function()) {
+ auto result = vm.call(method.as_function(), const_cast<Object*>(this));
+ if (!result.is_object())
+ return result;
+ }
}
- 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)
diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h
index a1d76957a3..93e9daac69 100644
--- a/Libraries/LibJS/Runtime/Object.h
+++ b/Libraries/LibJS/Runtime/Object.h
@@ -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]; }
diff --git a/Libraries/LibJS/Runtime/Value.cpp b/Libraries/LibJS/Runtime/Value.cpp
index 953a3b70ad..a0b6a4f96d 100644
--- a/Libraries/LibJS/Runtime/Value.cpp
+++ b/Libraries/LibJS/Runtime/Value.cpp
@@ -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);
diff --git a/Libraries/LibJS/Tests/ordinary-to-primitive.js b/Libraries/LibJS/Tests/ordinary-to-primitive.js
new file mode 100644
index 0000000000..176beadb76
--- /dev/null
+++ b/Libraries/LibJS/Tests/ordinary-to-primitive.js
@@ -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");
+});