Commit ad887bd5 authored by Frank Tang's avatar Frank Tang Committed by Commit Bot

[Intl] Connect BigInt toLocaleString to nf

Bug: v8:8704
Change-Id: Ib0548a6aa9f4b148d412de5632c1652f529371fa
Reviewed-on: https://chromium-review.googlesource.com/c/1424021
Commit-Queue: Frank Tang <ftang@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59050}
parent 3cc69194
......@@ -4654,6 +4654,10 @@ void Genesis::InitializeGlobal_harmony_intl_segmenter() {
}
}
void Genesis::InitializeGlobal_harmony_intl_bigint() {
// No new functions.
}
#endif // V8_INTL_SUPPORT
void Genesis::InitializeGlobal_harmony_object_from_entries() {
......
......@@ -7,6 +7,9 @@
#include "src/conversions.h"
#include "src/counters.h"
#include "src/objects-inl.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/intl-objects.h"
#endif
namespace v8 {
namespace internal {
......@@ -123,6 +126,16 @@ Object BigIntToStringImpl(Handle<Object> receiver, Handle<Object> radix,
BUILTIN(BigIntPrototypeToLocaleString) {
HandleScope scope(isolate);
#ifdef V8_INTL_SUPPORT
if (FLAG_harmony_intl_bigint) {
RETURN_RESULT_OR_FAILURE(
isolate, Intl::NumberToLocaleString(isolate, args.receiver(),
args.atOrUndefined(isolate, 1),
args.atOrUndefined(isolate, 2)));
}
// Fallbacks to old toString implemention if flag is off or no
// V8_INTL_SUPPORT
#endif // V8_INTL_SUPPORT
Handle<Object> radix = isolate->factory()->undefined_value();
return BigIntToStringImpl(args.receiver(), radix, isolate,
"BigInt.prototype.toLocaleString");
......
......@@ -82,14 +82,19 @@ BUILTIN(NumberFormatPrototypeFormatToParts) {
Handle<Object> x;
if (args.length() >= 2) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, x,
Object::ToNumber(isolate, args.at(1)));
if (FLAG_harmony_intl_bigint) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, x, Object::ToNumeric(isolate, args.at(1)));
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, x,
Object::ToNumber(isolate, args.at(1)));
}
} else {
x = isolate->factory()->nan_value();
}
RETURN_RESULT_OR_FAILURE(isolate, JSNumberFormat::FormatToParts(
isolate, number_format, x->Number()));
RETURN_RESULT_OR_FAILURE(
isolate, JSNumberFormat::FormatToParts(isolate, number_format, x));
}
BUILTIN(DateTimeFormatPrototypeResolvedOptions) {
......@@ -400,19 +405,23 @@ BUILTIN(NumberFormatInternalFormatNumber) {
// 3. If value is not provided, let value be undefined.
Handle<Object> value = args.atOrUndefined(isolate, 1);
// 4. Let x be ? ToNumber(value).
Handle<Object> number_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, number_obj,
Object::ToNumber(isolate, value));
// 4. Let x be ? ToNumeric(value).
Handle<Object> numeric_obj;
if (FLAG_harmony_intl_bigint) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, numeric_obj,
Object::ToNumeric(isolate, value));
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, numeric_obj,
Object::ToNumber(isolate, value));
}
double number = number_obj->Number();
icu::NumberFormat* icu_number_format =
number_format->icu_number_format()->raw();
CHECK_NOT_NULL(icu_number_format);
// Return FormatNumber(nf, x).
RETURN_RESULT_OR_FAILURE(isolate, JSNumberFormat::FormatNumber(
isolate, *icu_number_format, number));
RETURN_RESULT_OR_FAILURE(
isolate,
JSNumberFormat::FormatNumeric(isolate, *icu_number_format, numeric_obj));
}
BUILTIN(DateTimeFormatConstructor) {
......
......@@ -204,9 +204,10 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields)
V(harmony_weak_refs, "harmony weak references") \
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(harmony_locale, "Intl.Locale")
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(harmony_locale, "Intl.Locale") \
V(harmony_intl_bigint, "BigInt.prototype.toLocaleString")
#else
#define HARMONY_INPROGRESS(V) HARMONY_INPROGRESS_BASE(V)
#endif
......
......@@ -181,6 +181,8 @@ class V8_EXPORT_PRIVATE BigInt : public BigIntBase {
return is_zero() ? 0 : ComputeLongHash(static_cast<uint64_t>(digit(0)));
}
bool IsNegative() const { return sign(); }
static bool EqualToString(Isolate* isolate, Handle<BigInt> x,
Handle<String> y);
static bool EqualToNumber(Handle<BigInt> x, Handle<Object> y);
......
......@@ -994,11 +994,14 @@ MaybeHandle<String> Intl::NumberToLocaleString(Isolate* isolate,
Handle<Object> num,
Handle<Object> locales,
Handle<Object> options) {
Handle<Object> number_obj;
ASSIGN_RETURN_ON_EXCEPTION(isolate, number_obj,
Object::ToNumber(isolate, num), String);
double number = number_obj->Number();
Handle<Object> numeric_obj;
if (FLAG_harmony_intl_bigint) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, numeric_obj,
Object::ToNumeric(isolate, num), String);
} else {
ASSIGN_RETURN_ON_EXCEPTION(isolate, numeric_obj,
Object::ToNumber(isolate, num), String);
}
// We only cache the instance when both locales and options are undefined,
// as that is the only case when the specified side-effects of examining
......@@ -1011,8 +1014,8 @@ MaybeHandle<String> Intl::NumberToLocaleString(Isolate* isolate,
Isolate::ICUObjectCacheType::kDefaultNumberFormat));
// We may use the cached icu::NumberFormat for a fast path.
if (cached_number_format != nullptr) {
return JSNumberFormat::FormatNumber(isolate, *cached_number_format,
number);
return JSNumberFormat::FormatNumeric(isolate, *cached_number_format,
numeric_obj);
}
}
......@@ -1036,7 +1039,8 @@ MaybeHandle<String> Intl::NumberToLocaleString(Isolate* isolate,
// Return FormatNumber(numberFormat, x).
icu::NumberFormat* icu_number_format =
number_format->icu_number_format()->raw();
return JSNumberFormat::FormatNumber(isolate, *icu_number_format, number);
return JSNumberFormat::FormatNumeric(isolate, *icu_number_format,
numeric_obj);
}
namespace {
......
......@@ -461,10 +461,45 @@ Handle<String> JSNumberFormat::CurrencyDisplayAsString() const {
}
}
MaybeHandle<String> JSNumberFormat::FormatNumber(
Isolate* isolate, const icu::NumberFormat& number_format, double number) {
namespace {
Maybe<icu::UnicodeString> IcuFormatNumber(
Isolate* isolate, const icu::NumberFormat& number_format,
Handle<Object> numeric_obj, icu::FieldPositionIterator* fp_iter) {
icu::UnicodeString result;
number_format.format(number, result);
// If it is BigInt, handle it differently.
UErrorCode status = U_ZERO_ERROR;
if (numeric_obj->IsBigInt()) {
Handle<BigInt> big_int = Handle<BigInt>::cast(numeric_obj);
Handle<String> big_int_string;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, big_int_string,
BigInt::ToString(isolate, big_int),
Nothing<icu::UnicodeString>());
number_format.format(
{big_int_string->ToCString().get(), big_int_string->length()}, result,
fp_iter, status);
} else {
double number = numeric_obj->Number();
number_format.format(number, result, fp_iter, status);
}
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(isolate,
NewTypeError(MessageTemplate::kIcuError),
Nothing<icu::UnicodeString>());
}
return Just(result);
}
} // namespace
MaybeHandle<String> JSNumberFormat::FormatNumeric(
Isolate* isolate, const icu::NumberFormat& number_format,
Handle<Object> numeric_obj) {
DCHECK(numeric_obj->IsNumeric());
Maybe<icu::UnicodeString> maybe_format =
IcuFormatNumber(isolate, number_format, numeric_obj, nullptr);
MAYBE_RETURN(maybe_format, Handle<String>());
icu::UnicodeString result = maybe_format.FromJust();
return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
......@@ -490,13 +525,22 @@ bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
// The list comes from third_party/icu/source/i18n/unicode/unum.h.
// They're mapped to NumberFormat part types mentioned throughout
// https://tc39.github.io/ecma402/#sec-partitionnumberpattern .
Handle<String> IcuNumberFieldIdToNumberType(int32_t field_id, double number,
Handle<String> IcuNumberFieldIdToNumberType(int32_t field_id,
Handle<Object> numeric_obj,
Isolate* isolate) {
DCHECK(numeric_obj->IsNumeric());
switch (static_cast<UNumberFormatFields>(field_id)) {
case UNUM_INTEGER_FIELD:
if (std::isfinite(number)) return isolate->factory()->integer_string();
if (std::isnan(number)) return isolate->factory()->nan_string();
return isolate->factory()->infinity_string();
if (numeric_obj->IsBigInt()) {
// Neither NaN nor Infinite could be stored into BigInt
// so just return integer.
return isolate->factory()->integer_string();
} else {
double number = numeric_obj->Number();
if (std::isfinite(number)) return isolate->factory()->integer_string();
if (std::isnan(number)) return isolate->factory()->nan_string();
return isolate->factory()->infinity_string();
}
case UNUM_FRACTION_FIELD:
return isolate->factory()->fraction_string();
case UNUM_DECIMAL_SEPARATOR_FIELD:
......@@ -508,9 +552,15 @@ Handle<String> IcuNumberFieldIdToNumberType(int32_t field_id, double number,
case UNUM_PERCENT_FIELD:
return isolate->factory()->percentSign_string();
case UNUM_SIGN_FIELD:
return number < 0 ? isolate->factory()->minusSign_string()
: isolate->factory()->plusSign_string();
if (numeric_obj->IsBigInt()) {
Handle<BigInt> big_int = Handle<BigInt>::cast(numeric_obj);
return big_int->IsNegative() ? isolate->factory()->minusSign_string()
: isolate->factory()->plusSign_string();
} else {
double number = numeric_obj->Number();
return number < 0 ? isolate->factory()->minusSign_string()
: isolate->factory()->plusSign_string();
}
case UNUM_EXPONENT_SYMBOL_FIELD:
case UNUM_EXPONENT_SIGN_FIELD:
case UNUM_EXPONENT_FIELD:
......@@ -625,16 +675,15 @@ std::vector<NumberFormatSpan> FlattenRegionsToParts(
Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
Handle<JSArray> result,
int start_index,
const icu::NumberFormat& fmt,
double number, Handle<String> unit) {
icu::UnicodeString formatted;
const icu::NumberFormat& number_format,
Handle<Object> numeric_obj,
Handle<String> unit) {
DCHECK(numeric_obj->IsNumeric());
icu::FieldPositionIterator fp_iter;
UErrorCode status = U_ZERO_ERROR;
fmt.format(number, formatted, &fp_iter, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewTypeError(MessageTemplate::kIcuError), Nothing<int>());
}
Maybe<icu::UnicodeString> maybe_format =
IcuFormatNumber(isolate, number_format, numeric_obj, &fp_iter);
MAYBE_RETURN(maybe_format, Nothing<int>());
icu::UnicodeString formatted = maybe_format.FromJust();
int32_t length = formatted.length();
int index = start_index;
......@@ -662,7 +711,7 @@ Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
Handle<String> field_type_string =
part.field_id == -1
? isolate->factory()->literal_string()
: IcuNumberFieldIdToNumberType(part.field_id, number, isolate);
: IcuNumberFieldIdToNumberType(part.field_id, numeric_obj, isolate);
Handle<String> substring;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, substring,
......@@ -681,7 +730,9 @@ Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
}
MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
Isolate* isolate, Handle<JSNumberFormat> number_format, double number) {
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj) {
CHECK(numeric_obj->IsNumeric());
Factory* factory = isolate->factory();
icu::NumberFormat* fmt = number_format->icu_number_format()->raw();
CHECK_NOT_NULL(fmt);
......@@ -689,7 +740,7 @@ MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
Handle<JSArray> result = factory->NewJSArray(0);
Maybe<int> maybe_format_to_parts = JSNumberFormat::FormatToParts(
isolate, result, 0, *fmt, number, Handle<String>());
isolate, result, 0, *fmt, numeric_obj, Handle<String>());
MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
return result;
......
......@@ -44,7 +44,8 @@ class JSNumberFormat : public JSObject {
Handle<JSNumberFormat> number_format);
V8_WARN_UNUSED_RESULT static MaybeHandle<JSArray> FormatToParts(
Isolate* isolate, Handle<JSNumberFormat> number_format, double number);
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj);
// A utility function used by the above JSNumberFormat::FormatToParts()
// and JSRelativeTimeFormat::FormatToParts().
......@@ -56,10 +57,12 @@ class JSNumberFormat : public JSObject {
// unit as "unit" to each added object.
V8_WARN_UNUSED_RESULT static Maybe<int> FormatToParts(
Isolate* isolate, Handle<JSArray> result, int start_index,
const icu::NumberFormat& fmt, double number, Handle<String> unit);
const icu::NumberFormat& fmt, Handle<Object> numeric_obj,
Handle<String> unit);
V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormatNumber(
Isolate* isolate, const icu::NumberFormat& number_format, double number);
V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormatNumeric(
Isolate* isolate, const icu::NumberFormat& number_format,
Handle<Object> numeric_obj);
static std::set<std::string> GetAvailableLocales();
......
......@@ -273,8 +273,9 @@ MaybeHandle<JSArray> GenerateRelativeTimeFormatParts(
Handle<String> unit = UnitAsString(isolate, unit_enum);
Maybe<int> maybe_format_to_parts =
JSNumberFormat::FormatToParts(isolate, array, index, nf, number, unit);
Handle<Object> number_obj = factory->NewNumber(number);
Maybe<int> maybe_format_to_parts = JSNumberFormat::FormatToParts(
isolate, array, index, nf, number_obj, unit);
MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
index = maybe_format_to_parts.FromJust();
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-bigint
var locales = [
"en", // "1,234,567,890,123,456"
"de", // "1.234.567.890.123.456"
"fr", // "1 234 567 890 123 456"
"hi", // "1,23,45,67,89,01,23,456"
"fa", // "۱٬۲۳۴٬۵۶۷٬۸۹۰٬۱۲۳٬۴۵۶"
"th-u-nu-thai", // "๑,๒๓๔,๕๖๗,๘๙๐,๑๒๓,๔๕๖"
];
var data = [
Number.MAX_SAFE_INTEGER,
-Number.MAX_SAFE_INTEGER,
Math.floor(Number.MAX_SAFE_INTEGER / 2),
0,
/// -0, // this case is broken now.
];
for (var locale of locales) {
let nf = new Intl.NumberFormat(locale);
let percentOption = {style: "percent"};
let nfPercent = new Intl.NumberFormat(locale, percentOption);
for (var n of data) {
let bigint = BigInt(n);
// Test NumberFormat w/ number output the same as
// BigInt.prototype.toLocaleString()
assertEquals(nf.format(n), bigint.toLocaleString(locale));
// Test NumberFormat output the same regardless pass in as number or BigInt
assertEquals(nf.format(n), nf.format(bigint));
// Test formatToParts
assertEquals(nf.formatToParts(n), nf.formatToParts(bigint));
// Test output with option
// Test NumberFormat w/ number output the same as
// BigInt.prototype.toLocaleString()
assertEquals(nfPercent.format(n),
bigint.toLocaleString(locale, percentOption));
// Test NumberFormat output the same regardless pass in as number or BigInt
assertEquals(nfPercent.format(n), nfPercent.format(bigint));
assertEquals(nfPercent.formatToParts(n), nfPercent.formatToParts(bigint));
}
// Test very big BigInt
let veryBigInt = BigInt(Number.MAX_SAFE_INTEGER) *
BigInt(Number.MAX_SAFE_INTEGER) *
BigInt(Number.MAX_SAFE_INTEGER);
assertEquals(nf.format(veryBigInt), veryBigInt.toLocaleString(locale));
// It should output different than toString
assertFalse(veryBigInt.toLocaleString(locale) == veryBigInt.toString());
assertTrue(veryBigInt.toLocaleString(locale).length >
veryBigInt.toString().length);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment