diff --git a/js/src/builtin/intl/CommonFunctions.js b/js/src/builtin/intl/CommonFunctions.js index 80a35f1851..69b96d6db1 100644 --- a/js/src/builtin/intl/CommonFunctions.js +++ b/js/src/builtin/intl/CommonFunctions.js @@ -698,29 +698,13 @@ function DefaultNumberOption(value, minimum, maximum, fallback) { * Number value, checks whether it is in the allowed range, and fills in a * fallback value if necessary. * - * Spec: ECMAScript Internationalization API Specification, 9.2.10. + * Spec: ECMAScript Internationalization API Specification, 9.2.12. */ function GetNumberOption(options, property, minimum, maximum, fallback) { - assert(typeof minimum === "number", "GetNumberOption"); - assert(typeof maximum === "number", "GetNumberOption"); - assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption"); - - // Step 1. - var value = options[property]; - - // Step 2. - if (value !== undefined) { - value = ToNumber(value); - if (Number_isNaN(value) || value < minimum || value > maximum) - ThrowRangeError(JSMSG_INVALID_DIGITS_VALUE, value); - return std_Math_floor(value); - } - - // Step 3. - return fallback; + // Steps 1-3. + return DefaultNumberOption(options[property], minimum, maximum, fallback); } - // Symbols in the self-hosting compartment can't be cloned, use a separate // object to hold the actual symbol value. // TODO: Can we add support to clone symbols? diff --git a/js/src/builtin/intl/NumberFormat.cpp b/js/src/builtin/intl/NumberFormat.cpp index 4dbc711d68..76d5753efd 100644 --- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -353,10 +353,6 @@ NewUNumberFormat(JSContext* cx, Handle numberFormat) return nullptr; uMaximumSignificantDigits = value.toInt32(); } else { - if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits, - &value)) - return nullptr; - uMinimumIntegerDigits = AssertedCast(value.toInt32()); if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits, &value)) return nullptr; @@ -367,6 +363,11 @@ NewUNumberFormat(JSContext* cx, Handle numberFormat) uMaximumFractionDigits = AssertedCast(value.toInt32()); } + if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits, + &value)) + return nullptr; + uMinimumIntegerDigits = AssertedCast(value.toInt32()); + if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value)) return nullptr; uUseGrouping = value.toBoolean(); diff --git a/js/src/builtin/intl/NumberFormat.js b/js/src/builtin/intl/NumberFormat.js index 2d6420ee7a..495aaa8542 100644 --- a/js/src/builtin/intl/NumberFormat.js +++ b/js/src/builtin/intl/NumberFormat.js @@ -55,8 +55,14 @@ function resolveNumberFormatInternals(lazyNumberFormatData) { // Step 22. internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits; - internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits; - internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits; + + if ("minimumFractionDigits" in lazyNumberFormatData) { + // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the + // actual presence (versus undefined-ness) of these properties. + assert("maximumFractionDigits" in lazyNumberFormatData, "min/max frac digits mismatch"); + internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits; + internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits; + } if ("minimumSignificantDigits" in lazyNumberFormatData) { // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the @@ -122,33 +128,74 @@ function UnwrapNumberFormat(nf, methodName) { * * Spec: ECMAScript Internationalization API Specification, 11.1.1. */ -function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault) { +function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault, mxfdDefault) { // We skip step 1 because we set the properties on a lazyData object. // Steps 2-4. assert(IsObject(options), "SetNumberFormatDigitOptions"); assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions"); - // Steps 5-8. - const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1); - const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault); - const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20); + assert(typeof mxfdDefault === "number", "SetNumberFormatDigitOptions"); + assert(mnfdDefault <= mxfdDefault, "SetNumberFormatDigitOptions"); - // Steps 9-10. + // Steps 5-9. + const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1); + let mnfd = options.minimumFractionDigits; + let mxfd = options.maximumFractionDigits; let mnsd = options.minimumSignificantDigits; let mxsd = options.maximumSignificantDigits; - // Steps 9-11. + // Step 10. lazyData.minimumIntegerDigits = mnid; - lazyData.minimumFractionDigits = mnfd; - lazyData.maximumFractionDigits = mxfd; + + // Step 11. + if (mnsd !== undefined || mxsd !== undefined) { + // Step 11.a (Omitted). + + // Step 11.b. + mnsd = DefaultNumberOption(mnsd, 1, 21, 1); + + // Step 11.c. + mxsd = DefaultNumberOption(mxsd, mnsd, 21, 21); + + // Step 11.d. + lazyData.minimumSignificantDigits = mnsd; + + // Step 11.e. + lazyData.maximumSignificantDigits = mxsd; + } // Step 12. - if (mnsd !== undefined || mxsd !== undefined) { - mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1); - mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21); - lazyData.minimumSignificantDigits = mnsd; - lazyData.maximumSignificantDigits = mxsd; + else if (mnfd !== undefined || mxfd !== undefined) { + // Step 12.a (Omitted). + + // Step 12.b. + mnfd = DefaultNumberOption(mnfd, 0, 20, mnfdDefault); + + // Step 12.c. + const mxfdActualDefault = std_Math_max(mnfd, mxfdDefault); + + // Step 12.d. + mxfd = DefaultNumberOption(mxfd, mnfd, 20, mxfdActualDefault); + + // Step 12.e. + lazyData.minimumFractionDigits = mnfd; + + // Step 12.f. + lazyData.maximumFractionDigits = mxfd; + } + + // Step 13 (TODO: Not yet implemented). + + // Step 14. + else { + // Step 14.a (Omitted). + + // Step 14.b. + lazyData.minimumFractionDigits = mnfdDefault; + + // Step 14.c. + lazyData.maximumFractionDigits = mxfdDefault; } } @@ -291,19 +338,16 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) { if (s === "currency") lazyNumberFormatData.currencyDisplay = cd; - // Steps 20-22. - SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0); - - // Step 25. - if (lazyNumberFormatData.maximumFractionDigits === undefined) { - let mxfdDefault = s === "currency" - ? cDigits - : s === "percent" - ? 0 - : 3; - lazyNumberFormatData.maximumFractionDigits = - std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault); + // Steps 22-25. + var mnfdDefault, mxfdDefault; + if (s === "currency") { + mnfdDefault = cDigits; + mxfdDefault = cDigits; + } else { + mnfdDefault = 0; + mxfdDefault = s === "percent" ? 0 : 3; } + SetNumberFormatDigitOptions(lazyNumberFormatData, options, mnfdDefault, mxfdDefault); // Steps 23. var g = GetOption(options, "useGrouping", "boolean", undefined, true); @@ -520,8 +564,6 @@ function Intl_NumberFormat_resolvedOptions() { numberingSystem: internals.numberingSystem, style: internals.style, minimumIntegerDigits: internals.minimumIntegerDigits, - minimumFractionDigits: internals.minimumFractionDigits, - maximumFractionDigits: internals.maximumFractionDigits, useGrouping: internals.useGrouping }; @@ -536,6 +578,15 @@ function Intl_NumberFormat_resolvedOptions() { _DefineDataProperty(result, "currencyDisplay", internals.currencyDisplay); } + // Min/Max fraction digits are either both present or not present at all. + assert(hasOwn("minimumFractionDigits", internals) === + hasOwn("maximumFractionDigits", internals), + "minimumFractionDigits is present iff maximumFractionDigits is present"); + if (hasOwn("minimumFractionDigits", internals)) { + _DefineDataProperty(result, "minimumFractionDigits", internals.minimumFractionDigits); + _DefineDataProperty(result, "maximumFractionDigits", internals.maximumFractionDigits); + } + // Min/Max significant digits are either both present or not at all. assert(hasOwn("minimumSignificantDigits", internals) === hasOwn("maximumSignificantDigits", internals), diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp index a610d0e3a0..135bb14348 100644 --- a/js/src/builtin/intl/PluralRules.cpp +++ b/js/src/builtin/intl/PluralRules.cpp @@ -224,11 +224,6 @@ NewUNumberFormatForPluralRules(JSContext* cx, Handle pluralR return nullptr; uMaximumSignificantDigits = value.toInt32(); } else { - if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits, - &value)) - return nullptr; - uMinimumIntegerDigits = AssertedCast(value.toInt32()); - if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits, &value)) return nullptr; @@ -240,6 +235,11 @@ NewUNumberFormatForPluralRules(JSContext* cx, Handle pluralR uMaximumFractionDigits = AssertedCast(value.toInt32()); } + if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits, + &value)) + return nullptr; + uMinimumIntegerDigits = AssertedCast(value.toInt32()); + UErrorCode status = U_ZERO_ERROR; UNumberFormat* nf = unum_open(UNUM_DECIMAL, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status); if (U_FAILURE(status)) { diff --git a/js/src/builtin/intl/PluralRules.js b/js/src/builtin/intl/PluralRules.js index 4522b5c4a3..d88fa9ada6 100644 --- a/js/src/builtin/intl/PluralRules.js +++ b/js/src/builtin/intl/PluralRules.js @@ -53,8 +53,12 @@ function resolvePluralRulesInternals(lazyPluralRulesData) { // Step 9. internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits; - internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits; - internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits; + + if ("minimumFractionDigits" in lazyPluralRulesData) { + assert("maximumFractionDigits" in lazyPluralRulesData, "min/max frac digits mismatch"); + internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits; + internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits; + } if ("minimumSignificantDigits" in lazyPluralRulesData) { assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch"); @@ -149,14 +153,8 @@ function InitializePluralRules(pluralRules, locales, options) { const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal"); lazyPluralRulesData.type = type; - // Step 9. - SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0); - - // Step 12. - if (lazyPluralRulesData.maximumFractionDigits === undefined) { - lazyPluralRulesData.maximumFractionDigits = - std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3); - } + // Steps 11-12. + SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0, 3); // Step 15. // @@ -246,11 +244,19 @@ function Intl_PluralRules_resolvedOptions() { type: internals.type, pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0), minimumIntegerDigits: internals.minimumIntegerDigits, - minimumFractionDigits: internals.minimumFractionDigits, - maximumFractionDigits: internals.maximumFractionDigits, }; - // Min/Max significant digits are either both present or not at all. + // Min/Max fraction digits are either both present or not present at all. + assert(hasOwn("minimumFractionDigits", internals) === + hasOwn("maximumFractionDigits", internals), + "minimumFractionDigits is present iff maximumFractionDigits is present"); + + if (hasOwn("minimumFractionDigits", internals)) { + _DefineDataProperty(result, "minimumFractionDigits", internals.minimumFractionDigits); + _DefineDataProperty(result, "maximumFractionDigits", internals.maximumFractionDigits); + } + + // Min/Max significant digits are either both present or not present at all. assert(hasOwn("minimumSignificantDigits", internals) === hasOwn("maximumSignificantDigits", internals), "minimumSignificantDigits is present iff maximumSignificantDigits is present");