summaryrefslogtreecommitdiff
path: root/js/src/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin')
-rw-r--r--js/src/builtin/BigInt.cpp232
-rw-r--r--js/src/builtin/BigInt.h50
-rw-r--r--js/src/builtin/BigInt.js34
-rw-r--r--js/src/builtin/MapObject.cpp15
-rw-r--r--js/src/builtin/Object.cpp9
-rw-r--r--js/src/builtin/ReflectParse.cpp11
-rw-r--r--js/src/builtin/TypedArray.js26
-rw-r--r--js/src/builtin/TypedObject.h22
-rw-r--r--js/src/builtin/TypedObjectConstants.h10
-rw-r--r--js/src/builtin/intl/CommonFunctions.h4
-rw-r--r--js/src/builtin/intl/NumberFormat.cpp92
-rw-r--r--js/src/builtin/intl/NumberFormat.h2
-rw-r--r--js/src/builtin/intl/NumberFormat.js6
-rw-r--r--js/src/builtin/intl/PluralRules.cpp4
14 files changed, 463 insertions, 54 deletions
diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp
new file mode 100644
index 0000000000..8a630534a8
--- /dev/null
+++ b/js/src/builtin/BigInt.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/BigInt.h"
+
+#include "jsapi.h"
+
+#include "builtin/TypedObject.h"
+#include "gc/Tracer.h"
+#include "js/TracingAPI.h"
+#include "vm/ArrayBufferObject.h"
+#include "vm/BigIntType.h"
+#include "vm/SelfHosting.h"
+#include "vm/TaggedProto.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+static MOZ_ALWAYS_INLINE bool
+IsBigInt(HandleValue v)
+{
+ return v.isBigInt() || (v.isObject() && v.toObject().is<BigIntObject>());
+}
+
+static JSObject*
+CreateBigIntPrototype(JSContext* cx, JSProtoKey key)
+{
+ return GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global());
+}
+
+// BigInt proposal section 5.1.3
+static bool
+BigIntConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (args.isConstructing()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR, "BigInt");
+ return false;
+ }
+
+ // Step 2.
+ RootedValue v(cx, args.get(0));
+ if (!ToPrimitive(cx, JSTYPE_NUMBER, &v))
+ return false;
+
+ // Steps 3-4.
+ BigInt* bi = v.isNumber()
+ ? NumberToBigInt(cx, v.toNumber())
+ : ToBigInt(cx, v);
+ if (!bi)
+ return false;
+
+ args.rval().setBigInt(bi);
+ return true;
+}
+
+JSObject*
+BigIntObject::create(JSContext* cx, HandleBigInt bigInt)
+{
+ RootedObject obj(cx, NewBuiltinClassInstance(cx, &class_));
+ if (!obj)
+ return nullptr;
+ BigIntObject& bn = obj->as<BigIntObject>();
+ bn.setFixedSlot(PRIMITIVE_VALUE_SLOT, BigIntValue(bigInt));
+ return &bn;
+}
+
+BigInt*
+BigIntObject::unbox() const
+{
+ return getFixedSlot(PRIMITIVE_VALUE_SLOT).toBigInt();
+}
+
+// BigInt proposal section 5.3.4
+bool
+BigIntObject::valueOf_impl(JSContext* cx, const CallArgs& args)
+{
+ // Step 1.
+ HandleValue thisv = args.thisv();
+ MOZ_ASSERT(IsBigInt(thisv));
+ RootedBigInt bi(cx, thisv.isBigInt()
+ ? thisv.toBigInt()
+ : thisv.toObject().as<BigIntObject>().unbox());
+
+ args.rval().setBigInt(bi);
+ return true;
+}
+
+bool
+BigIntObject::valueOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsBigInt, valueOf_impl>(cx, args);
+}
+
+// BigInt proposal section 5.3.3
+bool
+BigIntObject::toString_impl(JSContext* cx, const CallArgs& args)
+{
+ // Step 1.
+ HandleValue thisv = args.thisv();
+ MOZ_ASSERT(IsBigInt(thisv));
+ RootedBigInt bi(cx, thisv.isBigInt()
+ ? thisv.toBigInt()
+ : thisv.toObject().as<BigIntObject>().unbox());
+
+ // Steps 2-3.
+ uint8_t radix = 10;
+
+ // Steps 4-5.
+ if (args.hasDefined(0)) {
+ double d;
+ if (!ToInteger(cx, args.get(0), &d))
+ return false;
+ if (d < 2 || d > 36) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_RADIX);
+ return false;
+ }
+ radix = d;
+ }
+
+ // Steps 6-7.
+ JSLinearString* str = BigInt::toString(cx, bi, radix);
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+bool
+BigIntObject::toString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsBigInt, toString_impl>(cx, args);
+}
+
+// BigInt proposal section 5.2.1. BigInt.asUintN ( bits, bigint )
+bool
+BigIntObject::asUintN(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ uint64_t bits;
+ if (!ToIndex(cx, args.get(0), &bits)) {
+ return false;
+ }
+
+ // Step 2.
+ RootedBigInt bi(cx, ToBigInt(cx, args.get(1)));
+ if (!bi) {
+ return false;
+ }
+
+ // Step 3.
+ BigInt* res = BigInt::asUintN(cx, bi, bits);
+ if (!res) {
+ return false;
+ }
+
+ args.rval().setBigInt(res);
+ return true;
+}
+
+// BigInt proposal section 5.2.2. BigInt.asIntN ( bits, bigint )
+bool
+BigIntObject::asIntN(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ uint64_t bits;
+ if (!ToIndex(cx, args.get(0), &bits)) {
+ return false;
+ }
+
+ // Step 2.
+ RootedBigInt bi(cx, ToBigInt(cx, args.get(1)));
+ if (!bi) {
+ return false;
+ }
+
+ // Step 3.
+ BigInt* res = BigInt::asIntN(cx, bi, bits);
+ if (!res) {
+ return false;
+ }
+
+ args.rval().setBigInt(res);
+ return true;
+}
+
+const ClassSpec BigIntObject::classSpec_ = {
+ GenericCreateConstructor<BigIntConstructor, 1, gc::AllocKind::FUNCTION>,
+ CreateBigIntPrototype,
+ BigIntObject::staticMethods,
+ nullptr,
+ BigIntObject::methods,
+ BigIntObject::properties
+};
+
+const Class BigIntObject::class_ = {
+ "BigInt",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_BigInt) |
+ JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
+ JS_NULL_CLASS_OPS,
+ &BigIntObject::classSpec_
+};
+
+const JSPropertySpec BigIntObject::properties[] = {
+ // BigInt proposal section 5.3.5
+ JS_STRING_SYM_PS(toStringTag, "BigInt", JSPROP_READONLY),
+ JS_PS_END
+};
+
+const JSFunctionSpec BigIntObject::methods[] = {
+ JS_FN("valueOf", valueOf, 0, 0),
+ JS_FN("toString", toString, 0, 0),
+ JS_SELF_HOSTED_FN("toLocaleString", "BigInt_toLocaleString", 0, 0),
+ JS_FS_END
+};
+
+const JSFunctionSpec BigIntObject::staticMethods[] = {
+ JS_FN("asUintN", asUintN, 2, 0),
+ JS_FN("asIntN", asIntN, 2, 0),
+ JS_FS_END
+};
diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h
new file mode 100644
index 0000000000..be447b2285
--- /dev/null
+++ b/js/src/builtin/BigInt.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef builtin_BigInt_h
+#define builtin_BigInt_h
+
+#include "js/Class.h"
+#include "js/RootingAPI.h"
+#include "vm/BigIntType.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+
+class GlobalObject;
+
+class BigIntObject : public NativeObject
+{
+ static const unsigned PRIMITIVE_VALUE_SLOT = 0;
+ static const unsigned RESERVED_SLOTS = 1;
+
+ public:
+ static const ClassSpec classSpec_;
+ static const Class class_;
+
+ static JSObject* create(JSContext* cx, JS::Handle<JS::BigInt*> bi);
+
+ // Methods defined on BigInt.prototype.
+ static bool valueOf_impl(JSContext* cx, const CallArgs& args);
+ static bool valueOf(JSContext* cx, unsigned argc, JS::Value* vp);
+ static bool toString_impl(JSContext* cx, const CallArgs& args);
+ static bool toString(JSContext* cx, unsigned argc, JS::Value* vp);
+ static bool asUintN(JSContext* cx, unsigned argc, JS::Value* vp);
+ static bool asIntN(JSContext* cx, unsigned argc, JS::Value* vp);
+
+ JS::BigInt* unbox() const;
+
+ private:
+ static const JSPropertySpec properties[];
+ static const JSFunctionSpec methods[];
+ static const JSFunctionSpec staticMethods[];
+};
+
+extern JSObject*
+InitBigIntClass(JSContext* cx, Handle<GlobalObject*> global);
+
+} // namespace js
+
+#endif
diff --git a/js/src/builtin/BigInt.js b/js/src/builtin/BigInt.js
new file mode 100644
index 0000000000..3ed3da5933
--- /dev/null
+++ b/js/src/builtin/BigInt.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Format this BigInt object into a string, using the locale and formatting
+ * options provided.
+ *
+ * Spec PR: https://github.com/tc39/ecma402/pull/236
+ */
+function BigInt_toLocaleString() {
+ // Step 1. Note that valueOf enforces "thisBigIntValue" restrictions.
+ var x = callFunction(std_BigInt_valueOf, this);
+
+ var locales = arguments.length > 0 ? arguments[0] : undefined;
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 2.
+ var numberFormat;
+ if (locales === undefined && options === undefined) {
+ // This cache only optimizes when no explicit locales and options
+ // arguments were supplied.
+ if (!IsRuntimeDefaultLocale(numberFormatCache.runtimeDefaultLocale)) {
+ numberFormatCache.numberFormat = intl_NumberFormat(locales, options);
+ numberFormatCache.runtimeDefaultLocale = RuntimeDefaultLocale();
+ }
+ numberFormat = numberFormatCache.numberFormat;
+ } else {
+ numberFormat = intl_NumberFormat(locales, options);
+ }
+
+ // Step 3.
+ return intl_FormatNumber(numberFormat, x, /* formatToParts = */ false);
+}
diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp
index 893e0448a4..fe748a6bde 100644
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -61,7 +61,7 @@ HashableValue::setValue(JSContext* cx, HandleValue v)
}
MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || value.isNumber() ||
- value.isString() || value.isSymbol() || value.isObject());
+ value.isString() || value.isSymbol() || value.isObject() || value.isBigInt());
return true;
}
@@ -81,6 +81,8 @@ HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs)
return v.toString()->asAtom().hash();
if (v.isSymbol())
return v.toSymbol()->hash();
+ if (v.isBigInt())
+ return MaybeForwarded(v.toBigInt())->hash();
if (v.isObject())
return hcs.scramble(v.asRawBits());
@@ -100,6 +102,12 @@ HashableValue::operator==(const HashableValue& other) const
// Two HashableValues are equal if they have equal bits.
bool b = (value.asRawBits() == other.value.asRawBits());
+ // BigInt values are considered equal if they represent the same
+ // mathematical value.
+ if (!b && (value.isBigInt() && other.value.isBigInt())) {
+ b = BigInt::equal(value.toBigInt(), other.value.toBigInt());
+ }
+
#ifdef DEBUG
bool same;
JS::RootingContext* rcx = GetJSContextFromMainThread();
@@ -378,8 +386,9 @@ MarkKey(Range& r, const HashableValue& key, JSTracer* trc)
HashableValue newKey = key.mark(trc);
if (newKey.get() != key.get()) {
- // The hash function only uses the bits of the Value, so it is safe to
- // rekey even when the object or string has been modified by the GC.
+ // The hash function must take account of the fact that the thing being
+ // hashed may have been moved by GC. This is only an issue for BigInt as for
+ // other types the hash function only uses the bits of the Value.
r.rekeyFront(newKey);
}
}
diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp
index 5221afb617..f4353480ba 100644
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -7,6 +7,8 @@
#include "mozilla/ArrayUtils.h"
+#include "builtin/BigInt.h"
+
#include "jscntxt.h"
#include "jsstr.h"
@@ -471,6 +473,9 @@ js::obj_toString(JSContext* cx, unsigned argc, Value* vp)
case ESClass::RegExp:
builtinTag = cx->names().objectRegExp;
break;
+ case ESClass::BigInt:
+ builtinTag = cx->names().objectBigInt;
+ break;
default:
if (obj->isCallable()) {
// Non-standard: Prevent <object> from showing up as Function.
@@ -837,7 +842,9 @@ EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPr
if (obj->is<NativeObject>()) {
HandleNativeObject nobj = obj.as<NativeObject>();
if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) {
- value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id));
+ if(!nobj->getDenseOrTypedArrayElement<CanGC>(cx, JSID_TO_INT(id), &value)) {
+ return false;
+ }
} else {
shape = nobj->lookup(cx, id);
if (!shape || !(shape->attributes() & JSPROP_ENUMERATE))
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
index f0b2001422..a8dd93aab2 100644
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -21,6 +21,7 @@
#include "frontend/TokenStream.h"
#include "js/CharacterEncoding.h"
#include "vm/RegExpObject.h"
+#include "vm/BigIntType.h"
#include "jsobjinlines.h"
@@ -3434,6 +3435,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
case PNK_STRING:
case PNK_REGEXP:
case PNK_NUMBER:
+ case PNK_BIGINT:
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
@@ -3604,7 +3606,7 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
case PNK_REGEXP:
{
- RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object);
+ RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object());
LOCAL_ASSERT(re1 && re1->is<RegExpObject>());
RootedObject re2(cx, CloneRegExpObject(cx, re1));
@@ -3619,6 +3621,13 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
val.setNumber(pn->as<NumericLiteral>().value());
break;
+ case PNK_BIGINT:
+ {
+ BigInt* x = pn->as<BigIntLiteral>().box()->value();
+ val.setBigInt(x);
+ break;
+ }
+
case PNK_NULL:
val.setNull();
break;
diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js
index 22023aa7ca..57f6d738ca 100644
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -879,7 +879,7 @@ function SetFromNonTypedArray(target, array, targetOffset, targetLength, targetB
// Steps 12-15, 21, 23-24.
while (targetOffset < limitOffset) {
// Steps 24a-c.
- var kNumber = ToNumber(src[k]);
+ var kNumber = ToNumeric(src[k]);
// Step 24d. This explicit check will be unnecessary when we implement
// throw-on-getting/setting-element-in-detached-buffer semantics.
@@ -1098,6 +1098,30 @@ function TypedArrayCompare(x, y) {
return Number_isNaN(y) ? -1 : 0;
}
+// https://tc39.github.io/proposal-bigint/#sec-%typedarray%.prototype.sort
+// TypedArray SortCompare specialization for BigInt values.
+function TypedArrayCompareBigInt(x, y) {
+ // Step 1.
+ // eslint-disable-next-line valid-typeof
+ assert(typeof x === "bigint" && typeof y === "bigint",
+ "x and y are not BigInts.");
+
+ // Step 2 (Implemented in TypedArraySort).
+
+ // Step 6.
+ if (x < y)
+ return -1;
+
+ // Step 7.
+ if (x > y)
+ return 1;
+
+ // Steps 3-5, 8-9 (Not applicable when sorting BigInt values).
+
+ // Step 10.
+ return 0;
+}
+
// TypedArray SortCompare specialization for integer values.
function TypedArrayCompareInt(x, y) {
// Step 1.
diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h
index 83700001d4..9318a0f795 100644
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -245,6 +245,10 @@ class ScalarTypeDescr : public SimpleTypeDescr
"TypedObjectConstants.h must be consistent with Scalar::Type");
static_assert(Scalar::Uint32 == JS_SCALARTYPEREPR_UINT32,
"TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::BigInt64 == JS_SCALARTYPEREPR_BIGINT64,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::BigUint64 == JS_SCALARTYPEREPR_BIGUINT64,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
static_assert(Scalar::Float32 == JS_SCALARTYPEREPR_FLOAT32,
"TypedObjectConstants.h must be consistent with Scalar::Type");
static_assert(Scalar::Float64 == JS_SCALARTYPEREPR_FLOAT64,
@@ -270,14 +274,16 @@ class ScalarTypeDescr : public SimpleTypeDescr
// unique C representation. In particular, omits Uint8Clamped since it
// is just a Uint8.
#define JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_) \
- macro_(Scalar::Int8, int8_t, int8) \
- macro_(Scalar::Uint8, uint8_t, uint8) \
- macro_(Scalar::Int16, int16_t, int16) \
- macro_(Scalar::Uint16, uint16_t, uint16) \
- macro_(Scalar::Int32, int32_t, int32) \
- macro_(Scalar::Uint32, uint32_t, uint32) \
- macro_(Scalar::Float32, float, float32) \
- macro_(Scalar::Float64, double, float64)
+ macro_(Scalar::Int8, int8_t, int8) \
+ macro_(Scalar::Uint8, uint8_t, uint8) \
+ macro_(Scalar::Int16, int16_t, int16) \
+ macro_(Scalar::Uint16, uint16_t, uint16) \
+ macro_(Scalar::Int32, int32_t, int32) \
+ macro_(Scalar::Uint32, uint32_t, uint32) \
+ macro_(Scalar::Float32, float, float32) \
+ macro_(Scalar::Float64, double, float64) \
+ macro_(Scalar::BigInt64, int64_t, bigint64) \
+ macro_(Scalar::BigUint64, uint64_t, biguint64)
// Must be in same order as the enum ScalarTypeDescr::Type:
#define JS_FOR_EACH_SCALAR_TYPE_REPR(macro_) \
diff --git a/js/src/builtin/TypedObjectConstants.h b/js/src/builtin/TypedObjectConstants.h
index a28c9159a5..aa930d29bb 100644
--- a/js/src/builtin/TypedObjectConstants.h
+++ b/js/src/builtin/TypedObjectConstants.h
@@ -88,10 +88,12 @@
#define JS_SCALARTYPEREPR_FLOAT32 6
#define JS_SCALARTYPEREPR_FLOAT64 7
#define JS_SCALARTYPEREPR_UINT8_CLAMPED 8
-#define JS_SCALARTYPEREPR_FLOAT32X4 11
-#define JS_SCALARTYPEREPR_INT8X16 12
-#define JS_SCALARTYPEREPR_INT16X8 13
-#define JS_SCALARTYPEREPR_INT32X4 14
+#define JS_SCALARTYPEREPR_BIGINT64 9
+#define JS_SCALARTYPEREPR_BIGUINT64 10
+#define JS_SCALARTYPEREPR_FLOAT32X4 13
+#define JS_SCALARTYPEREPR_INT8X16 14
+#define JS_SCALARTYPEREPR_INT16X8 15
+#define JS_SCALARTYPEREPR_INT32X4 16
// These constants are for use exclusively in JS code. In C++ code,
// prefer ReferenceTypeRepresentation::TYPE_ANY etc, which allows
diff --git a/js/src/builtin/intl/CommonFunctions.h b/js/src/builtin/intl/CommonFunctions.h
index c6856c9b6d..8c538a489b 100644
--- a/js/src/builtin/intl/CommonFunctions.h
+++ b/js/src/builtin/intl/CommonFunctions.h
@@ -18,6 +18,8 @@
#include "js/Vector.h"
#include "vm/String.h"
+#include "jscntxt.h"
+
namespace JS { class Value; }
class JSObject;
@@ -159,4 +161,4 @@ CallICU(JSContext* cx, const ICUStringFunction& strFn)
} // namespace js
-#endif /* builtin_intl_CommonFunctions_h */ \ No newline at end of file
+#endif /* builtin_intl_CommonFunctions_h */
diff --git a/js/src/builtin/intl/NumberFormat.cpp b/js/src/builtin/intl/NumberFormat.cpp
index 9ee3b02109..8820166f56 100644
--- a/js/src/builtin/intl/NumberFormat.cpp
+++ b/js/src/builtin/intl/NumberFormat.cpp
@@ -33,6 +33,7 @@ using namespace js;
using mozilla::AssertedCast;
using mozilla::IsFinite;
+using mozilla::IsNegative;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using js::intl::CallICU;
@@ -401,24 +402,51 @@ NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
}
static JSString*
-PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double* x,
+PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, HandleValue x,
UFieldPositionIterator* fpositer)
{
- // PartitionNumberPattern doesn't consider -0.0 to be negative.
- if (IsNegativeZero(*x))
- *x = 0.0;
-
- return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
- return unum_formatDoubleForFields(nf, *x, chars, size, fpositer, status);
- });
+ if (x.isNumber()) {
+ double num = x.toNumber();
+
+ // PartitionNumberPattern doesn't consider -0.0 to be negative.
+ if (IsNegativeZero(num))
+ num = 0.0;
+
+ return CallICU(cx, [nf, num, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
+ return unum_formatDoubleForFields(nf, num, chars, size, fpositer, status);
+ });
+ } else if(x.isBigInt()) {
+ RootedBigInt bi(cx, x.toBigInt());
+ int64_t num;
+
+ if (BigInt::isInt64(bi, &num)) {
+ return CallICU(cx, [nf, num](UChar* chars, int32_t size, UErrorCode* status) {
+ return unum_formatInt64(nf, num, chars, size, nullptr, status);
+ });
+ } else {
+ JSLinearString* str = BigInt::toString(cx, bi, 10);
+ if (!str) {
+ return nullptr;
+ }
+ MOZ_ASSERT(str->hasLatin1Chars());
+
+ JS::AutoCheckCannotGC noGC(cx);
+ const char* latinchars = reinterpret_cast<const char*>(str->latin1Chars(noGC));
+ size_t length = str->length();
+ return CallICU(cx, [nf, latinchars, length](UChar* chars, int32_t size, UErrorCode* status) {
+ return unum_formatDecimal(nf, latinchars, length, chars, size, nullptr, status);
+ });
+ }
+ }
+ return nullptr;
}
bool
-js::intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
+js::FormatNumeric(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result)
{
// Passing null for |fpositer| will just not compute partition information,
// letting us common up all ICU number-formatting code.
- JSString* str = PartitionNumberPattern(cx, nf, &x, nullptr);
+ JSString* str = PartitionNumberPattern(cx, nf, x, nullptr);
if (!str)
return false;
@@ -429,7 +457,7 @@ js::intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleV
using FieldType = ImmutablePropertyNamePtr JSAtomState::*;
static FieldType
-GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d)
+GetFieldTypeForNumberField(UNumberFormatFields fieldName, HandleValue x)
{
// See intl/icu/source/i18n/unicode/unum.h for a detailed field list. This
// list is deliberately exhaustive: cases might have to be added/removed if
@@ -438,10 +466,15 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d)
// version-testing #ifdefs, should cross-version divergence occur.
switch (fieldName) {
case UNUM_INTEGER_FIELD:
- if (IsNaN(d))
- return &JSAtomState::nan;
- if (!IsFinite(d))
- return &JSAtomState::infinity;
+ if (x.isNumber()) {
+ double d = x.toNumber();
+ if (IsNaN(d)) {
+ return &JSAtomState::nan;
+ }
+ if (!IsFinite(d)) {
+ return &JSAtomState::infinity;
+ }
+ }
return &JSAtomState::integer;
case UNUM_GROUPING_SEPARATOR_FIELD:
@@ -454,13 +487,17 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d)
return &JSAtomState::fraction;
case UNUM_SIGN_FIELD: {
- MOZ_ASSERT(!IsNegativeZero(d),
- "-0 should have been excluded by PartitionNumberPattern");
-
- // Manual trawling through the ICU call graph appears to indicate that
- // the basic formatting we request will never include a positive sign.
- // But this analysis may be mistaken, so don't absolutely trust it.
- return d < 0 ? &JSAtomState::minusSign : &JSAtomState::plusSign;
+ // Manual trawling through the ICU call graph appears to indicate that
+ // the basic formatting we request will never include a positive sign.
+ // But this analysis may be mistaken, so don't absolutely trust it.
+ MOZ_ASSERT(!x.isNumber() || !IsNaN(x.toNumber()),
+ "ICU appearing not to produce positive-sign among fields, "
+ "plus our coercing all NaNs to one with sign bit unset "
+ "(i.e. \"positive\"), means we shouldn't reach here with a "
+ "NaN value");
+ bool isNegative =
+ x.isNumber() ? IsNegative(x.toNumber()) : x.toBigInt()->isNegative();
+ return isNegative ? &JSAtomState::minusSign : &JSAtomState::plusSign;
}
case UNUM_PERCENT_FIELD:
@@ -495,7 +532,7 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d)
}
static bool
-intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
+FormatNumericToParts(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result)
{
UErrorCode status = U_ZERO_ERROR;
@@ -508,7 +545,7 @@ intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHand
MOZ_ASSERT(fpositer);
ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer);
- RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer));
+ RootedString overallResult(cx, PartitionNumberPattern(cx, nf, x, fpositer));
if (!overallResult)
return false;
@@ -824,7 +861,7 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isObject());
- MOZ_ASSERT(args[1].isNumber());
+ MOZ_ASSERT(args[1].isNumeric());
MOZ_ASSERT(args[2].isBoolean());
Rooted<NumberFormatObject*> numberFormat(cx, &args[0].toObject().as<NumberFormatObject>());
@@ -842,8 +879,7 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
// Use the UNumberFormat to actually format the number.
if (args[2].toBoolean()) {
- return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval());
+ return FormatNumericToParts(cx, nf, args.get(1), args.rval());
}
- return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
+ return FormatNumeric(cx, nf, args.get(1), args.rval());
}
-
diff --git a/js/src/builtin/intl/NumberFormat.h b/js/src/builtin/intl/NumberFormat.h
index befa0c3e0d..bc2f659527 100644
--- a/js/src/builtin/intl/NumberFormat.h
+++ b/js/src/builtin/intl/NumberFormat.h
@@ -71,7 +71,7 @@ intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
-intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result);
+FormatNumeric(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result);
} // namespace js
diff --git a/js/src/builtin/intl/NumberFormat.js b/js/src/builtin/intl/NumberFormat.js
index 238a59405b..261bff1dc6 100644
--- a/js/src/builtin/intl/NumberFormat.js
+++ b/js/src/builtin/intl/NumberFormat.js
@@ -454,14 +454,14 @@ function numberFormatFormatToBind(value) {
// ES5.1 10.5, step 4.d.ii.
// Step 1.a.ii-iii.
- var x = ToNumber(value);
+ var x = ToNumeric(value);
return intl_FormatNumber(this, x, /* formatToParts = */ false);
}
/**
* Returns a function bound to this NumberFormat that returns a String value
- * representing the result of calling ToNumber(value) according to the
+ * representing the result of calling ToNumeric(value) according to the
* effective locale and the formatting options of this NumberFormat.
*
* Spec: ECMAScript Internationalization API Specification, 11.4.3.
@@ -497,7 +497,7 @@ function Intl_NumberFormat_formatToParts(value) {
getNumberFormatInternals(nf);
// Step 4.
- var x = ToNumber(value);
+ var x = ToNumeric(value);
// Step 5.
return intl_FormatNumber(nf, x, /* formatToParts = */ true);
diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp
index e1e8e37044..ce2f3c3893 100644
--- a/js/src/builtin/intl/PluralRules.cpp
+++ b/js/src/builtin/intl/PluralRules.cpp
@@ -292,8 +292,6 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
if (!type)
return false;
- double x = args[1].toNumber();
-
// We need a NumberFormat in order to format the number
// using the number formatting options (minimum/maximum*Digits)
// before we push the result to PluralRules
@@ -302,7 +300,7 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
// API: http://bugs.icu-project.org/trac/ticket/12763
//
RootedValue fmtNumValue(cx);
- if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
+ if (!FormatNumeric(cx, nf, args[1], &fmtNumValue))
return false;
RootedString fmtNumValueString(cx, fmtNumValue.toString());
AutoStableStringChars stableChars(cx);