From adc3de44803356b6f5381d3b83c0650715ce7ae8 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sun, 6 Jun 2021 06:56:08 +0100 Subject: [PATCH] LibJS: Implement Number.prototype.valueOf() --- Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 2 +- .../LibJS/Runtime/NumberPrototype.cpp | 35 +++++++++++++------ .../Libraries/LibJS/Runtime/NumberPrototype.h | 1 + .../Number/Number.prototype.toString.js | 5 ++- .../Number/Number.prototype.valueOf.js | 21 +++++++++++ 5 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.valueOf.js diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 8b11a0faa8..c23e43a612 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -53,7 +53,7 @@ M(NotASymbol, "{} is not a symbol") \ M(NotIterable, "{} is not iterable") \ M(NonExtensibleDefine, "Cannot define property {} on non-extensible object") \ - M(NumberIncompatibleThis, "Number.prototype.{} method called with incompatible this target") \ + M(NumberIncompatibleThis, "Number.prototype.{}() called with incompatible this target") \ M(ObjectDefinePropertyReturnedFalse, "Object's [[DefineProperty]] method returned false") \ M(ObjectFreezeFailed, "Could not freeze object") \ M(ObjectSealFailed, "Could not seal object") \ diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp index cfc793ee09..eda66d8609 100644 --- a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2021, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause */ @@ -32,26 +33,33 @@ void NumberPrototype::initialize(GlobalObject& object) { auto& vm = this->vm(); Object::initialize(object); - define_native_function(vm.names.toString, to_string, 1, Attribute::Configurable | Attribute::Writable); + u8 attr = Attribute::Configurable | Attribute::Writable; + define_native_function(vm.names.toString, to_string, 1, attr); + define_native_function(vm.names.valueOf, value_of, 0, attr); } NumberPrototype::~NumberPrototype() { } +// https://tc39.es/ecma262/#thisnumbervalue +static Value this_number_value(GlobalObject& global_object, StringView const& name) +{ + auto& vm = global_object.vm(); + auto this_value = vm.this_value(global_object); + if (this_value.is_number()) + return this_value; + if (this_value.is_object() && is(this_value.as_object())) + return static_cast(this_value.as_object()).value_of(); + vm.throw_exception(global_object, ErrorType::NumberIncompatibleThis, name); + return {}; +} + JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_string) { - Value number_value; - - auto this_value = vm.this_value(global_object); - if (this_value.is_number()) { - number_value = this_value; - } else if (this_value.is_object() && is(this_value.as_object())) { - number_value = static_cast(this_value.as_object()).value_of(); - } else { - vm.throw_exception(global_object, ErrorType::NumberIncompatibleThis, "toString"); + auto number_value = this_number_value(global_object, "toString"); + if (vm.exception()) return {}; - } int radix; auto argument = vm.argument(0); @@ -123,4 +131,9 @@ JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_string) return js_string(vm, String(characters.data(), characters.size())); } +JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::value_of) +{ + return this_number_value(global_object, "valueOf"); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.h b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h index 65e95751f9..dece015294 100644 --- a/Userland/Libraries/LibJS/Runtime/NumberPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h @@ -19,6 +19,7 @@ public: virtual ~NumberPrototype() override; JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(value_of); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js index eebb53d6cf..da478dcf08 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js @@ -75,7 +75,10 @@ describe("correct behavior", () => { test("errors", () => { test("must be called with numeric |this|", () => { [true, [], {}, Symbol("foo"), "bar", 1n].forEach(value => { - expect(() => Number.prototype.toString.call(value)).toThrow(TypeError); + expect(() => Number.prototype.toString.call(value)).toThrowWithMessage( + TypeError, + "Number.prototype.toString() called with incompatible this target" + ); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.valueOf.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.valueOf.js new file mode 100644 index 0000000000..eb9cae2c59 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.valueOf.js @@ -0,0 +1,21 @@ +describe("correct behavior", () => { + test("length", () => { + expect(Number.prototype.valueOf).toHaveLength(0); + }); + + test("basic functionality", () => { + expect(new Number(42).valueOf()).toBe(42); + expect(Number.prototype.valueOf.call(42)).toBe(42); + }); +}); + +test("errors", () => { + test("must be called with numeric |this|", () => { + [true, [], {}, Symbol("foo"), "bar", 1n].forEach(value => { + expect(() => Number.prototype.valueOf.call(value)).toThrowWithMessage( + TypeError, + "Number.prototype.valueOf() called with incompatible this target" + ); + }); + }); +});