mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 13:58:49 +00:00
Issue #2026 - Part 2a - Support BigInt in NumberFormat and toLocaleString. https://bugzilla.mozilla.org/show_bug.cgi?id=1543677
This commit is contained in:
@@ -553,6 +553,10 @@ class MOZ_NON_PARAM alignas(8) Value
|
||||
return isObject() || isNull();
|
||||
}
|
||||
|
||||
bool isNumeric() const {
|
||||
return isNumber() || isBigInt();
|
||||
}
|
||||
|
||||
bool isGCThing() const {
|
||||
#if defined(JS_NUNBOX32)
|
||||
/* gcc sometimes generates signed < without explicit casts. */
|
||||
@@ -1410,6 +1414,7 @@ class WrappedPtrOperations<JS::Value, Wrapper>
|
||||
|
||||
bool isNullOrUndefined() const { return value().isNullOrUndefined(); }
|
||||
bool isObjectOrNull() const { return value().isObjectOrNull(); }
|
||||
bool isNumeric() const { return value().isNumeric(); }
|
||||
|
||||
bool toBoolean() const { return value().toBoolean(); }
|
||||
double toNumber() const { return value().toNumber(); }
|
||||
|
||||
@@ -139,32 +139,6 @@ BigIntObject::toString(JSContext* cx, unsigned argc, Value* vp)
|
||||
return CallNonGenericMethod<IsBigInt, toString_impl>(cx, args);
|
||||
}
|
||||
|
||||
// BigInt proposal section 5.3.2. "This function is
|
||||
// implementation-dependent, and it is permissible, but not encouraged,
|
||||
// for it to return the same thing as toString."
|
||||
bool
|
||||
BigIntObject::toLocaleString_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
HandleValue thisv = args.thisv();
|
||||
MOZ_ASSERT(IsBigInt(thisv));
|
||||
RootedBigInt bi(cx, thisv.isBigInt()
|
||||
? thisv.toBigInt()
|
||||
: thisv.toObject().as<BigIntObject>().unbox());
|
||||
|
||||
RootedString str(cx, BigInt::toString(cx, bi, 10));
|
||||
if (!str)
|
||||
return false;
|
||||
args.rval().setString(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BigIntObject::toLocaleString(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsBigInt, toLocaleString_impl>(cx, args);
|
||||
}
|
||||
|
||||
// BigInt proposal section 5.2.1. BigInt.asUintN ( bits, bigint )
|
||||
bool
|
||||
BigIntObject::asUintN(JSContext* cx, unsigned argc, Value* vp)
|
||||
@@ -247,7 +221,7 @@ const JSPropertySpec BigIntObject::properties[] = {
|
||||
const JSFunctionSpec BigIntObject::methods[] = {
|
||||
JS_FN("valueOf", valueOf, 0, 0),
|
||||
JS_FN("toString", toString, 0, 0),
|
||||
JS_FN("toLocaleString", toLocaleString, 0, 0),
|
||||
JS_SELF_HOSTED_FN("toLocaleString", "BigInt_toLocaleString", 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@ class BigIntObject : public NativeObject
|
||||
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 toLocaleString_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool toLocaleString(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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,43 @@ 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;
|
||||
if (x.isNumber()) {
|
||||
double num = x.toNumber();
|
||||
|
||||
return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return unum_formatDoubleForFields(nf, *x, chars, size, fpositer, status);
|
||||
});
|
||||
// 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());
|
||||
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 +449,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 +458,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 +479,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 +524,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 +537,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 +853,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 +871,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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-2
@@ -1503,8 +1503,7 @@ js::ToInt8Slow(JSContext *cx, const HandleValue v, int8_t *out)
|
||||
bool
|
||||
js::ToNumericSlow(ExclusiveContext* cx, MutableHandleValue vp)
|
||||
{
|
||||
MOZ_ASSERT(!vp.isNumber());
|
||||
MOZ_ASSERT(!vp.isBigInt());
|
||||
MOZ_ASSERT(!vp.isNumeric());
|
||||
|
||||
// Step 1.
|
||||
if (!vp.isPrimitive()) {
|
||||
|
||||
+2
-3
@@ -378,10 +378,9 @@ ToNumericSlow(ExclusiveContext* cx, JS::MutableHandleValue vp);
|
||||
MOZ_ALWAYS_INLINE MOZ_MUST_USE bool
|
||||
ToNumeric(ExclusiveContext* cx, JS::MutableHandleValue vp)
|
||||
{
|
||||
if (vp.isNumber())
|
||||
return true;
|
||||
if (vp.isBigInt())
|
||||
if (vp.isNumeric()) {
|
||||
return true;
|
||||
}
|
||||
return ToNumericSlow(cx, vp);
|
||||
}
|
||||
|
||||
|
||||
@@ -705,6 +705,7 @@ selfhosted.inputs = [
|
||||
'builtin/Utilities.js',
|
||||
'builtin/Array.js',
|
||||
'builtin/AsyncIteration.js',
|
||||
'builtin/BigInt.js',
|
||||
'builtin/Classes.js',
|
||||
'builtin/Date.js',
|
||||
'builtin/Error.js',
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "jswrapper.h"
|
||||
#include "selfhosted.out.h"
|
||||
|
||||
#include "builtin/BigInt.h"
|
||||
#include "builtin/intl/Collator.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/IntlObject.h"
|
||||
@@ -2207,6 +2208,17 @@ static bool intrinsic_ToBigInt(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool intrinsic_ToNumeric(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
if (!ToNumeric(cx, args[0])) {
|
||||
return false;
|
||||
}
|
||||
args.rval().set(args[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The self-hosting global isn't initialized with the normal set of builtins.
|
||||
// Instead, individual C++-implemented functions that're required by
|
||||
// self-hosted code are defined as global functions. Accessing these
|
||||
@@ -2230,6 +2242,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_FN("std_Array_reverse", array_reverse, 0,0),
|
||||
JS_FNINFO("std_Array_splice", array_splice, &array_splice_info, 2,0),
|
||||
|
||||
JS_FN("std_BigInt_valueOf", BigIntObject::valueOf, 0,0),
|
||||
|
||||
JS_FN("std_Date_now", date_now, 0,0),
|
||||
JS_FN("std_Date_valueOf", date_valueOf, 0,0),
|
||||
|
||||
@@ -2637,7 +2651,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0),
|
||||
|
||||
JS_FN("ToBigInt", intrinsic_ToBigInt, 1, 0),
|
||||
|
||||
JS_FN("ToNumeric", intrinsic_ToNumeric, 1, 0),
|
||||
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user