WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
[patch]
Patch
bug-215438-20211024042545.patch (text/plain), 61.17 KB, created by
Yusuke Suzuki
on 2021-10-24 04:25:46 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Yusuke Suzuki
Created:
2021-10-24 04:25:46 PDT
Size:
61.17 KB
patch
obsolete
>Subversion Revision: 284754 >diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog >index d8a6ba93d88d0a817b179c6c9dda9ce2255ccca9..ecec782f2884c36e461f429dbb631e5b8fed1fdd 100644 >--- a/Source/JavaScriptCore/ChangeLog >+++ b/Source/JavaScriptCore/ChangeLog >@@ -1,3 +1,44 @@ >+2021-10-24 Yusuke Suzuki <ysuzuki@apple.com> >+ >+ [JSC] Implement IntlNumberFormat v3 >+ https://bugs.webkit.org/show_bug.cgi?id=215438 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * JavaScriptCore.xcodeproj/project.pbxproj: >+ * Sources.txt: >+ * runtime/CommonIdentifiers.h: >+ * runtime/IntlNumberFormat.cpp: >+ (JSC::IntlNumberFormat::initializeNumberFormat): >+ (JSC::IntlNumberFormat::signDisplayString): >+ (JSC::IntlNumberFormat::roundingModeString): >+ (JSC::IntlNumberFormat::trailingZeroDisplayString): >+ (JSC::IntlNumberFormat::roundingPriorityString): >+ (JSC::IntlNumberFormat::useGroupingValue): >+ (JSC::IntlNumberFormat::resolvedOptions const): >+ * runtime/IntlNumberFormat.h: >+ * runtime/IntlNumberFormatInlines.h: >+ (JSC::setNumberFormatDigitOptions): >+ * runtime/IntlNumberFormatPrototype.cpp: >+ (JSC::JSC_DEFINE_HOST_FUNCTION): >+ * runtime/IntlObjectInlines.h: >+ (JSC::intlStringOrBooleanOption): >+ * runtime/IntlPluralRules.cpp: >+ (JSC::UPluralRulesDeleter::operator()): >+ (JSC::UNumberRangeFormatterDeleter::operator()): >+ (JSC::IntlPluralRules::initializePluralRules): >+ (JSC::IntlPluralRules::resolvedOptions const): >+ (JSC::IntlPluralRules::select const): >+ (JSC::IntlPluralRules::selectRange const): >+ * runtime/IntlPluralRules.h: >+ * runtime/IntlPluralRulesPrototype.cpp: >+ (JSC::IntlPluralRulesPrototype::create): >+ (JSC::IntlPluralRulesPrototype::finishCreation): >+ (JSC::JSC_DEFINE_HOST_FUNCTION): >+ * runtime/IntlPluralRulesPrototype.h: >+ * runtime/JSBigInt.h: >+ (JSC::JSBigInt::tryExtractSafeDoubleInteger): >+ > 2021-10-22 Justin Michaud <justin_michaud@apple.com> > > Fix nits from 232019 >diff --git a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj >index 3df3a6186f63f83442df17ce29342042893f2bdf..84072a883584cb774f7a91e4229bdd23eac8cc64 100644 >--- a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj >+++ b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj >@@ -1845,6 +1845,7 @@ > E307178C24C7829800DF0644 /* IntlLocalePrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF930245A3CFC00C9BA3B /* IntlLocalePrototype.h */; }; > E307178D24C7829A00DF0644 /* IntlLocaleConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92C245A3CFA00C9BA3B /* IntlLocaleConstructor.h */; }; > E307178E24C7829D00DF0644 /* IntlLocale.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92B245A3CF900C9BA3B /* IntlLocale.h */; }; >+ E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7D5FB18F20744BF1005DDF64 /* IntlPluralRules.cpp */; }; > E30E8A5426DE2E4800DA4915 /* TemporalTimeZonePrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A4E26DE2E4700DA4915 /* TemporalTimeZonePrototype.h */; }; > E30E8A5626DE2E4800DA4915 /* TemporalTimeZone.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A5026DE2E4800DA4915 /* TemporalTimeZone.h */; }; > E30E8A5726DE2E4800DA4915 /* TemporalTimeZoneConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A5126DE2E4800DA4915 /* TemporalTimeZoneConstructor.h */; }; >@@ -11834,6 +11835,7 @@ > 5C4196622270E0000047B7CD /* InspectorBackendDispatcherCompatibility.cpp in Sources */, > E399AEC32559457F00B78485 /* IntlDateTimeFormat.cpp in Sources */, > E366441E254409B30001876F /* IntlListFormat.cpp in Sources */, >+ E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */, > A3EE8543262514B000FC9B8D /* IntlWorkaround.cpp in Sources */, > E38E8790254B978400F6F9E4 /* JSDateMath.cpp in Sources */, > 536B319E1F735F160037FC33 /* LowLevelInterpreter.cpp in Sources */, >diff --git a/Source/JavaScriptCore/Sources.txt b/Source/JavaScriptCore/Sources.txt >index e0e23a2f36c125e967f0efa5c4d9b98da88cec13..c221674bc6c3095b6692a203359e8f53c1cda84e 100644 >--- a/Source/JavaScriptCore/Sources.txt >+++ b/Source/JavaScriptCore/Sources.txt >@@ -840,7 +840,7 @@ runtime/IntlNumberFormat.cpp > runtime/IntlNumberFormatConstructor.cpp > runtime/IntlNumberFormatPrototype.cpp > runtime/IntlObject.cpp >-runtime/IntlPluralRules.cpp >+runtime/IntlPluralRules.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect to this file. > runtime/IntlPluralRulesConstructor.cpp > runtime/IntlPluralRulesPrototype.cpp > runtime/IntlRelativeTimeFormat.cpp >diff --git a/Source/JavaScriptCore/runtime/CommonIdentifiers.h b/Source/JavaScriptCore/runtime/CommonIdentifiers.h >index 7828d008a853461e48e36d2fbea61c709576ddaf..4ced52570cd95e46540bdc08567e80b9b7c911ca 100644 >--- a/Source/JavaScriptCore/runtime/CommonIdentifiers.h >+++ b/Source/JavaScriptCore/runtime/CommonIdentifiers.h >@@ -217,10 +217,12 @@ > macro(resolve) \ > macro(roundingIncrement) \ > macro(roundingMode) \ >+ macro(roundingPriority) \ > macro(script) \ > macro(second) \ > macro(seconds) \ > macro(segment) \ >+ macro(selectRange) \ > macro(sensitivity) \ > macro(set) \ > macro(size) \ >@@ -249,6 +251,7 @@ > macro(toLocaleString) \ > macro(toPrecision) \ > macro(toString) \ >+ macro(trailingZeroDisplay) \ > macro(type) \ > macro(uid) \ > macro(unicode) \ >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp >index fc3237032022b4c6d67dd81559ec335b56bd0dff..c1a2914c666baf0153da8cdc7103cee4b55ce9e1 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp >@@ -309,14 +309,53 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > setNumberFormatDigitOptions(globalObject, this, options, minimumFractionDigitsDefault, maximumFractionDigitsDefault, m_notation); > RETURN_IF_EXCEPTION(scope, void()); > >+ m_roundingIncrement = intlNumberOption(globalObject, options, vm.propertyNames->roundingIncrement, 1, 5000, 1); >+ RETURN_IF_EXCEPTION(scope, void()); >+ static constexpr const unsigned roundingIncrementCandidates[] = { >+ 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 >+ }; >+ bool invalidRoundingIncrement = true; >+ for (unsigned candidate : roundingIncrementCandidates) { >+ if (m_roundingIncrement == candidate) { >+ invalidRoundingIncrement = false; >+ break; >+ } >+ } >+ if (invalidRoundingIncrement) { >+ throwRangeError(globalObject, scope, "roundingIncrement must be one of 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000"_s); >+ return; >+ } >+ if (m_roundingIncrement != 1 && m_roundingType != IntlRoundingType::FractionDigits) { >+ throwRangeError(globalObject, scope, "rounding type is not fraction-digits while roundingIncrement is specified"_s); >+ return; >+ } >+ >+ m_trailingZeroDisplay = intlOption<TrailingZeroDisplay>(globalObject, options, vm.propertyNames->trailingZeroDisplay, { { "auto"_s, TrailingZeroDisplay::Auto }, { "stripIfInteger"_s, TrailingZeroDisplay::StripIfInteger } }, "trailingZeroDisplay must be either \"auto\" or \"stripIfInteger\""_s, TrailingZeroDisplay::Auto); >+ RETURN_IF_EXCEPTION(scope, void()); >+ > m_compactDisplay = intlOption<CompactDisplay>(globalObject, options, Identifier::fromString(vm, "compactDisplay"), { { "short"_s, CompactDisplay::Short }, { "long"_s, CompactDisplay::Long } }, "compactDisplay must be either \"short\" or \"long\""_s, CompactDisplay::Short); > RETURN_IF_EXCEPTION(scope, void()); > >- TriState useGrouping = intlBooleanOption(globalObject, options, Identifier::fromString(vm, "useGrouping")); >+ UseGrouping defaultUseGrouping = UseGrouping::Auto; >+ if (m_notation == IntlNotation::Compact) >+ defaultUseGrouping = UseGrouping::Min2; >+ >+ m_useGrouping = intlStringOrBooleanOption<UseGrouping>(globalObject, options, Identifier::fromString(vm, "useGrouping"), UseGrouping::Always, UseGrouping::False, { { "min2"_s, UseGrouping::Min2 }, { "auto"_s, UseGrouping::Auto }, { "always"_s, UseGrouping::Always } }, "useGrouping must be either true, false, \"min2\", \"auto\", or \"always\""_s, defaultUseGrouping); >+ >+ m_signDisplay = intlOption<SignDisplay>(globalObject, options, Identifier::fromString(vm, "signDisplay"), { { "auto"_s, SignDisplay::Auto }, { "never"_s, SignDisplay::Never }, { "always"_s, SignDisplay::Always }, { "exceptZero"_s, SignDisplay::ExceptZero }, { "negative"_s, SignDisplay::Negative } }, "signDisplay must be either \"auto\", \"never\", \"always\", \"exceptZero\", or \"negative\""_s, SignDisplay::Auto); > RETURN_IF_EXCEPTION(scope, void()); >- m_useGrouping = useGrouping != TriState::False; > >- m_signDisplay = intlOption<SignDisplay>(globalObject, options, Identifier::fromString(vm, "signDisplay"), { { "auto"_s, SignDisplay::Auto }, { "never"_s, SignDisplay::Never }, { "always"_s, SignDisplay::Always }, { "exceptZero"_s, SignDisplay::ExceptZero } }, "signDisplay must be either \"auto\", \"never\", \"always\", or \"exceptZero\""_s, SignDisplay::Auto); >+ m_roundingMode = intlOption<RoundingMode>(globalObject, options, vm.propertyNames->roundingMode, { >+ { "ceil"_s, RoundingMode::Ceil }, >+ { "floor"_s, RoundingMode::Floor }, >+ { "expand"_s, RoundingMode::Expand }, >+ { "trunc"_s, RoundingMode::Trunc }, >+ { "halfCeil"_s, RoundingMode::HalfCeil }, >+ { "halfFloor"_s, RoundingMode::HalfFloor }, >+ { "halfExpand"_s, RoundingMode::HalfExpand }, >+ { "halfTrunc"_s, RoundingMode::HalfTrunc }, >+ { "halfEven"_s, RoundingMode::HalfEven } >+ }, "roundingMode must be either \"ceil\", \"floor\", \"expand\", \"trunc\", \"halfCeil\", \"halfFloor\", \"halfExpand\", \"halfTrunc\", or \"halfEven\""_s, RoundingMode::HalfExpand); > RETURN_IF_EXCEPTION(scope, void()); > > CString dataLocaleWithExtensions = makeString(resolved.dataLocale, "-u-nu-", m_numberingSystem).utf8(); >@@ -329,7 +368,48 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md > > StringBuilder skeletonBuilder; >- skeletonBuilder.append("rounding-mode-half-up"); >+ >+ switch (m_roundingMode) { >+ case RoundingMode::Ceil: >+ skeletonBuilder.append("rounding-mode-ceiling"); >+ break; >+ case RoundingMode::Floor: >+ skeletonBuilder.append("rounding-mode-floor"); >+ break; >+ case RoundingMode::Expand: >+ skeletonBuilder.append("rounding-mode-up"); >+ break; >+ case RoundingMode::Trunc: >+ skeletonBuilder.append("rounding-mode-down"); >+ break; >+ case RoundingMode::HalfCeil: { >+ // Only ICU69~ supports half-ceiling. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21 >+ if (WTF::ICU::majorVersion() >= 69) >+ skeletonBuilder.append("rounding-mode-half-ceiling"); >+ else >+ skeletonBuilder.append("rounding-mode-half-up"); // Default option. >+ break; >+ } >+ case RoundingMode::HalfFloor: { >+ // Only ICU69~ supports half-ceil. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21 >+ if (WTF::ICU::majorVersion() >= 69) >+ skeletonBuilder.append("rounding-mode-half-floor"); >+ else >+ skeletonBuilder.append("rounding-mode-half-up"); // Default option. >+ break; >+ } >+ case RoundingMode::HalfExpand: >+ skeletonBuilder.append("rounding-mode-half-up"); >+ break; >+ case RoundingMode::HalfTrunc: >+ skeletonBuilder.append("rounding-mode-half-down"); >+ break; >+ case RoundingMode::HalfEven: >+ skeletonBuilder.append("rounding-mode-half-even"); >+ break; >+ } > > switch (m_style) { > case Style::Decimal: >@@ -391,27 +471,73 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > for (unsigned i = 0; i < m_minimumIntegerDigits; ++i) > skeletonBuilder.append('0'); > >- switch (m_roundingType) { >- case IntlRoundingType::FractionDigits: { >- // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision >- skeletonBuilder.append(" ."); >- for (unsigned i = 0; i < m_minimumFractionDigits; ++i) >- skeletonBuilder.append('0'); >- for (unsigned i = 0; i < m_maximumFractionDigits - m_minimumFractionDigits; ++i) >- skeletonBuilder.append('#'); >- break; >+ if (m_roundingIncrement != 1) { >+ skeletonBuilder.append(" precision-increment/"); >+ auto string = numberToStringUnsigned<Vector<LChar, 10>>(m_roundingIncrement); >+ if (m_maximumFractionDigits >= string.size()) { >+ skeletonBuilder.append("0."); >+ for (unsigned i = 0; i < (m_maximumFractionDigits - string.size()); ++i) >+ skeletonBuilder.append('0'); >+ skeletonBuilder.appendCharacters(string.data(), string.size()); >+ } else { >+ unsigned nonFraction = string.size() - m_maximumFractionDigits; >+ skeletonBuilder.appendCharacters(string.data(), nonFraction); >+ skeletonBuilder.append('.'); >+ skeletonBuilder.appendCharacters(string.data() + nonFraction, m_maximumFractionDigits); >+ } >+ } else { >+ switch (m_roundingType) { >+ case IntlRoundingType::FractionDigits: { >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision >+ skeletonBuilder.append(" ."); >+ for (unsigned i = 0; i < m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('0'); >+ for (unsigned i = 0; i < m_maximumFractionDigits - m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('#'); >+ break; >+ } >+ case IntlRoundingType::SignificantDigits: { >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision >+ skeletonBuilder.append(' '); >+ for (unsigned i = 0; i < m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('@'); >+ for (unsigned i = 0; i < m_maximumSignificantDigits - m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('#'); >+ break; >+ } >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: >+ // Before Intl.NumberFormat v3, it was CompactRounding mode, where we do not configure anything. >+ // So, if linked ICU is ~68, we do nothing. >+ if (WTF::ICU::majorVersion() >= 69) { >+ // https://github.com/unicode-org/icu/commit/d7db6c1f8655bb53153695b09a50029fd04a8364 >+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#precision >+ skeletonBuilder.append(" ."); >+ for (unsigned i = 0; i < m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('0'); >+ for (unsigned i = 0; i < m_maximumFractionDigits - m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('#'); >+ skeletonBuilder.append('/'); >+ for (unsigned i = 0; i < m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('@'); >+ for (unsigned i = 0; i < m_maximumSignificantDigits - m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('#'); >+ skeletonBuilder.append(m_roundingType == IntlRoundingType::MorePrecision ? 'r' : 's'); >+ } >+ break; >+ } > } >- case IntlRoundingType::SignificantDigits: { >- // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision >- skeletonBuilder.append(' '); >- for (unsigned i = 0; i < m_minimumSignificantDigits; ++i) >- skeletonBuilder.append('@'); >- for (unsigned i = 0; i < m_maximumSignificantDigits - m_minimumSignificantDigits; ++i) >- skeletonBuilder.append('#'); >+ >+ // Configure this just after precision. >+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#trailing-zero-display >+ switch (m_trailingZeroDisplay) { >+ case TrailingZeroDisplay::Auto: > break; >- } >- case IntlRoundingType::CompactRounding: >- // Do not set anything. >+ case TrailingZeroDisplay::StripIfInteger: >+ // Only ICU69~ supports trailing zero display. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/b79c299f90d4023ac237db3d0335d568bf21cd36 >+ if (WTF::ICU::majorVersion() >= 69) >+ skeletonBuilder.append("/w"); > break; > } > >@@ -462,10 +588,34 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > else > skeletonBuilder.append(" sign-except-zero"); > break; >+ case SignDisplay::Negative: >+ // Only ICU69~ supports negative sign display. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/1aa0dad8e06ecc99bff442dd37f6daa2d39d9a5a >+ if (WTF::ICU::majorVersion() >= 69) { >+ if (useAccounting) >+ skeletonBuilder.append(" sign-accounting-negative"); >+ else >+ skeletonBuilder.append(" sign-negative"); >+ } >+ break; > } > >- if (!m_useGrouping) >+ // https://github.com/tc39/proposal-intl-numberformat-v3/issues/3 >+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#grouping >+ switch (m_useGrouping) { >+ case UseGrouping::False: > skeletonBuilder.append(" group-off"); >+ break; >+ case UseGrouping::Min2: >+ skeletonBuilder.append(" group-min2"); >+ break; >+ case UseGrouping::Auto: >+ skeletonBuilder.append(" group-auto"); >+ break; >+ case UseGrouping::Always: >+ skeletonBuilder.append(" group-on-aligned"); >+ break; >+ } > > String skeleton = skeletonBuilder.toString(); > dataLogLnIf(IntlNumberFormatInternal::verbose, skeleton); >@@ -529,6 +679,7 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > case SignDisplay::Never: > case SignDisplay::Always: > case SignDisplay::ExceptZero: >+ case SignDisplay::Negative: > // Ignore this case since linked ICU does not support it. > break; > } >@@ -559,11 +710,27 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits); > unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits); > break; >- case IntlRoundingType::CompactRounding: >+ case IntlRoundingType::MorePrecision: >+ // Ignore this case since linked ICU does not support it. >+ break; >+ case IntlRoundingType::LessPrecision: >+ // Ignore this case since linked ICU does not support it. >+ break; >+ } >+ >+ switch (m_useGrouping) { >+ case UseGrouping::False: >+ unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, false); >+ break; >+ case UseGrouping::Min2: > // Ignore this case since linked ICU does not support it. > break; >+ case UseGrouping::Auto: >+ break; >+ case UseGrouping::Always: >+ unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, true); >+ break; > } >- unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, m_useGrouping); > unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); > #endif > } >@@ -727,11 +894,81 @@ ASCIILiteral IntlNumberFormat::signDisplayString(SignDisplay signDisplay) > return "always"_s; > case SignDisplay::ExceptZero: > return "exceptZero"_s; >+ case SignDisplay::Negative: >+ return "negative"_s; > } > ASSERT_NOT_REACHED(); > return ASCIILiteral::null(); > } > >+ASCIILiteral IntlNumberFormat::roundingModeString(RoundingMode roundingMode) >+{ >+ switch (roundingMode) { >+ case RoundingMode::Ceil: >+ return "ceil"_s; >+ case RoundingMode::Floor: >+ return "floor"_s; >+ case RoundingMode::Expand: >+ return "expand"_s; >+ case RoundingMode::Trunc: >+ return "trunc"_s; >+ case RoundingMode::HalfCeil: >+ return "halfCeil"_s; >+ case RoundingMode::HalfFloor: >+ return "halfFloor"_s; >+ case RoundingMode::HalfExpand: >+ return "halfExpand"_s; >+ case RoundingMode::HalfTrunc: >+ return "halfTrunc"_s; >+ case RoundingMode::HalfEven: >+ return "halfEven"_s; >+ } >+ ASSERT_NOT_REACHED(); >+ return ASCIILiteral::null(); >+} >+ >+ASCIILiteral IntlNumberFormat::trailingZeroDisplayString(TrailingZeroDisplay trailingZeroDisplay) >+{ >+ switch (trailingZeroDisplay) { >+ case TrailingZeroDisplay::Auto: >+ return "auto"_s; >+ case TrailingZeroDisplay::StripIfInteger: >+ return "stripIfInteger"_s; >+ } >+ ASSERT_NOT_REACHED(); >+ return ASCIILiteral::null(); >+} >+ >+ASCIILiteral IntlNumberFormat::roundingPriorityString(IntlRoundingType roundingType) >+{ >+ switch (roundingType) { >+ case IntlRoundingType::FractionDigits: >+ case IntlRoundingType::SignificantDigits: >+ return "auto"_s; >+ case IntlRoundingType::MorePrecision: >+ return "morePrecision"_s; >+ case IntlRoundingType::LessPrecision: >+ return "lessPrecision"_s; >+ } >+ ASSERT_NOT_REACHED(); >+ return ASCIILiteral::null(); >+} >+ >+JSValue IntlNumberFormat::useGroupingValue(VM& vm, UseGrouping useGrouping) >+{ >+ switch (useGrouping) { >+ case UseGrouping::False: >+ return jsBoolean(false); >+ case UseGrouping::Min2: >+ return jsNontrivialString(vm, "min2"_s); >+ case UseGrouping::Auto: >+ return jsNontrivialString(vm, "auto"_s); >+ case UseGrouping::Always: >+ return jsNontrivialString(vm, "always"_s); >+ } >+ return jsUndefined(); >+} >+ > // https://tc39.es/ecma402/#sec-intl.numberformat.prototype.resolvedoptions > JSObject* IntlNumberFormat::resolvedOptions(JSGlobalObject* globalObject) const > { >@@ -764,14 +1001,23 @@ JSObject* IntlNumberFormat::resolvedOptions(JSGlobalObject* globalObject) const > options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); > options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; >- case IntlRoundingType::CompactRounding: >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: >+ options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; > } >- options->putDirect(vm, Identifier::fromString(vm, "useGrouping"), jsBoolean(m_useGrouping)); >+ options->putDirect(vm, Identifier::fromString(vm, "useGrouping"), useGroupingValue(vm, m_useGrouping)); > options->putDirect(vm, Identifier::fromString(vm, "notation"), jsNontrivialString(vm, notationString(m_notation))); > if (m_notation == IntlNotation::Compact) > options->putDirect(vm, Identifier::fromString(vm, "compactDisplay"), jsNontrivialString(vm, compactDisplayString(m_compactDisplay))); > options->putDirect(vm, Identifier::fromString(vm, "signDisplay"), jsNontrivialString(vm, signDisplayString(m_signDisplay))); >+ options->putDirect(vm, vm.propertyNames->roundingMode, jsNontrivialString(vm, roundingModeString(m_roundingMode))); >+ options->putDirect(vm, vm.propertyNames->roundingIncrement, jsNumber(m_roundingIncrement)); >+ options->putDirect(vm, vm.propertyNames->trailingZeroDisplay, jsNontrivialString(vm, trailingZeroDisplayString(m_trailingZeroDisplay))); >+ options->putDirect(vm, vm.propertyNames->roundingPriority, jsNontrivialString(vm, roundingPriorityString(m_roundingType))); > return options; > } > >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormat.h b/Source/JavaScriptCore/runtime/IntlNumberFormat.h >index 38a770c59d410700f89c49f769a9149dbca857de..b4acffdf9cd26e03c1a4691b04183c6f97f06c29 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormat.h >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormat.h >@@ -48,7 +48,8 @@ class IntlFieldIterator; > class JSBoundFunction; > enum class RelevantExtensionKey : uint8_t; > >-enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, CompactRounding }; >+enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, MorePrecision, LessPrecision }; >+enum class IntlRoundingPriority : uint8_t { Auto, MorePrecision, LessPrecision }; > enum class IntlNotation : uint8_t { Standard, Scientific, Engineering, Compact }; > template<typename IntlType> void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation); > >@@ -94,6 +95,8 @@ class IntlNumberFormat final : public JSNonFinalObject { > > static IntlNumberFormat* unwrapForOldFunctions(JSGlobalObject*, JSValue); > >+ static ASCIILiteral roundingPriorityString(IntlRoundingType); >+ > private: > IntlNumberFormat(VM&, Structure*); > void finishCreation(VM&); >@@ -105,7 +108,10 @@ class IntlNumberFormat final : public JSNonFinalObject { > enum class CurrencySign : uint8_t { Standard, Accounting }; > enum class UnitDisplay : uint8_t { Short, Narrow, Long }; > enum class CompactDisplay : uint8_t { Short, Long }; >- enum class SignDisplay : uint8_t { Auto, Never, Always, ExceptZero }; >+ enum class SignDisplay : uint8_t { Auto, Never, Always, ExceptZero, Negative }; >+ enum class TrailingZeroDisplay : uint8_t { Auto, StripIfInteger }; >+ enum class UseGrouping : uint8_t { False, Min2, Auto, Always }; >+ enum class RoundingMode : uint8_t { Ceil, Floor, Expand, Trunc, HalfCeil, HalfFloor, HalfExpand, HalfTrunc, HalfEven }; > > static ASCIILiteral styleString(Style); > static ASCIILiteral currencyDisplayString(CurrencyDisplay); >@@ -113,6 +119,9 @@ class IntlNumberFormat final : public JSNonFinalObject { > static ASCIILiteral unitDisplayString(UnitDisplay); > static ASCIILiteral compactDisplayString(CompactDisplay); > static ASCIILiteral signDisplayString(SignDisplay); >+ static ASCIILiteral roundingModeString(RoundingMode); >+ static ASCIILiteral trailingZeroDisplayString(TrailingZeroDisplay); >+ static JSValue useGroupingValue(VM&, UseGrouping); > > WriteBarrier<JSBoundFunction> m_boundFormat; > #if HAVE(ICU_U_NUMBER_FORMATTER) >@@ -133,6 +142,7 @@ class IntlNumberFormat final : public JSNonFinalObject { > unsigned m_maximumFractionDigits { 3 }; > unsigned m_minimumSignificantDigits { 0 }; > unsigned m_maximumSignificantDigits { 0 }; >+ unsigned m_roundingIncrement { 1 }; > Style m_style { Style::Decimal }; > CurrencyDisplay m_currencyDisplay; > CurrencySign m_currencySign; >@@ -140,7 +150,9 @@ class IntlNumberFormat final : public JSNonFinalObject { > CompactDisplay m_compactDisplay; > IntlNotation m_notation { IntlNotation::Standard }; > SignDisplay m_signDisplay; >- bool m_useGrouping { true }; >+ TrailingZeroDisplay m_trailingZeroDisplay { TrailingZeroDisplay::Auto }; >+ UseGrouping m_useGrouping { UseGrouping::Always }; >+ RoundingMode m_roundingMode { RoundingMode::HalfExpand }; > IntlRoundingType m_roundingType { IntlRoundingType::FractionDigits }; > }; > >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h b/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h >index 23d3863d1600431ef74df40c874471766d05169a..f44e173a8fe753f88f82de2e9473296b35b15cad 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h >@@ -61,47 +61,69 @@ void setNumberFormatDigitOptions(JSGlobalObject* globalObject, IntlType* intlIns > > intlInstance->m_minimumIntegerDigits = minimumIntegerDigits; > >- if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) { >- intlInstance->m_roundingType = IntlRoundingType::SignificantDigits; >- unsigned minimumSignificantDigits = intlDefaultNumberOption(globalObject, minimumSignificantDigitsValue, vm.propertyNames->minimumSignificantDigits, 1, 21, 1); >- RETURN_IF_EXCEPTION(scope, void()); >- unsigned maximumSignificantDigits = intlDefaultNumberOption(globalObject, maximumSignificantDigitsValue, vm.propertyNames->maximumSignificantDigits, minimumSignificantDigits, 21, 21); >- RETURN_IF_EXCEPTION(scope, void()); >- intlInstance->m_minimumSignificantDigits = minimumSignificantDigits; >- intlInstance->m_maximumSignificantDigits = maximumSignificantDigits; >- return; >- } >+ IntlRoundingPriority roundingPriority = intlOption<IntlRoundingPriority>(globalObject, options, vm.propertyNames->roundingPriority, { { "auto"_s, IntlRoundingPriority::Auto }, { "morePrecision"_s, IntlRoundingPriority::MorePrecision }, { "lessPrecision"_s, IntlRoundingPriority::LessPrecision } }, "roundingPriority must be either \"auto\", \"morePrecision\", or \"lessPrecision\""_s, IntlRoundingPriority::Auto); >+ RETURN_IF_EXCEPTION(scope, void()); > >- if (!minimumFractionDigitsValue.isUndefined() || !maximumFractionDigitsValue.isUndefined()) { >- constexpr unsigned undefinedValue = UINT32_MAX; >- intlInstance->m_roundingType = IntlRoundingType::FractionDigits; >- unsigned specifiedMinimumFractionDigits = intlDefaultNumberOption(globalObject, minimumFractionDigitsValue, vm.propertyNames->minimumFractionDigits, 0, 20, undefinedValue); >- RETURN_IF_EXCEPTION(scope, void()); >- unsigned specifiedMaximumFractionDigits = intlDefaultNumberOption(globalObject, maximumFractionDigitsValue, vm.propertyNames->maximumFractionDigits, 0, 20, undefinedValue); >- RETURN_IF_EXCEPTION(scope, void()); >- if (specifiedMaximumFractionDigits != undefinedValue) >- minimumFractionDigitsDefault = std::min(minimumFractionDigitsDefault, specifiedMaximumFractionDigits); >- unsigned minimumFractionDigits = intlDefaultNumberOption(globalObject, (specifiedMinimumFractionDigits == undefinedValue) ? jsUndefined() : jsNumber(specifiedMinimumFractionDigits), vm.propertyNames->minimumFractionDigits, 0, 20, minimumFractionDigitsDefault); >- RETURN_IF_EXCEPTION(scope, void()); >- unsigned maximumFractionDigits = intlDefaultNumberOption(globalObject, (specifiedMaximumFractionDigits == undefinedValue) ? jsUndefined() : jsNumber(specifiedMaximumFractionDigits), vm.propertyNames->maximumFractionDigits, 0, 20, std::max(maximumFractionDigitsDefault, minimumFractionDigits)); >- RETURN_IF_EXCEPTION(scope, void()); >- if (minimumFractionDigits > maximumFractionDigits) { >- throwRangeError(globalObject, scope, "Computed minimumFractionDigits is larger than maximumFractionDigits"_s); >- return; >+ bool hasSd = !minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined(); >+ bool hasFd = !minimumFractionDigitsValue.isUndefined() || !maximumFractionDigitsValue.isUndefined(); >+ bool needSd = hasSd || roundingPriority != IntlRoundingPriority::Auto; >+ bool needFd = (!hasSd && notation != IntlNotation::Compact) || roundingPriority != IntlRoundingPriority::Auto; >+ >+ if (needSd) { >+ if (hasSd) { >+ unsigned minimumSignificantDigits = intlDefaultNumberOption(globalObject, minimumSignificantDigitsValue, vm.propertyNames->minimumSignificantDigits, 1, 21, 1); >+ RETURN_IF_EXCEPTION(scope, void()); >+ unsigned maximumSignificantDigits = intlDefaultNumberOption(globalObject, maximumSignificantDigitsValue, vm.propertyNames->maximumSignificantDigits, minimumSignificantDigits, 21, 21); >+ RETURN_IF_EXCEPTION(scope, void()); >+ intlInstance->m_minimumSignificantDigits = minimumSignificantDigits; >+ intlInstance->m_maximumSignificantDigits = maximumSignificantDigits; >+ } else { >+ intlInstance->m_minimumSignificantDigits = 1; >+ intlInstance->m_maximumSignificantDigits = 21; > } >- intlInstance->m_minimumFractionDigits = minimumFractionDigits; >- intlInstance->m_maximumFractionDigits = maximumFractionDigits; >- return; > } > >- if (notation == IntlNotation::Compact) { >- intlInstance->m_roundingType = IntlRoundingType::CompactRounding; >- return; >+ if (needFd) { >+ if (hasFd) { >+ constexpr unsigned undefinedValue = UINT32_MAX; >+ unsigned minimumFractionDigits = intlDefaultNumberOption(globalObject, minimumFractionDigitsValue, vm.propertyNames->minimumFractionDigits, 0, 20, undefinedValue); >+ RETURN_IF_EXCEPTION(scope, void()); >+ unsigned maximumFractionDigits = intlDefaultNumberOption(globalObject, maximumFractionDigitsValue, vm.propertyNames->maximumFractionDigits, 0, 20, undefinedValue); >+ RETURN_IF_EXCEPTION(scope, void()); >+ >+ if (minimumFractionDigits == undefinedValue) >+ minimumFractionDigits = std::min(minimumFractionDigitsDefault, maximumFractionDigits); >+ else if (maximumFractionDigits == undefinedValue) >+ maximumFractionDigits = std::max(maximumFractionDigitsDefault, minimumFractionDigits); >+ else if (minimumFractionDigits > maximumFractionDigits) { >+ throwRangeError(globalObject, scope, "Computed minimumFractionDigits is larger than maximumFractionDigits"_s); >+ return; >+ } >+ >+ intlInstance->m_minimumFractionDigits = minimumFractionDigits; >+ intlInstance->m_maximumFractionDigits = maximumFractionDigits; >+ } else { >+ intlInstance->m_minimumFractionDigits = minimumFractionDigitsDefault; >+ intlInstance->m_maximumFractionDigits = maximumFractionDigitsDefault; >+ } > } > >- intlInstance->m_roundingType = IntlRoundingType::FractionDigits; >- intlInstance->m_minimumFractionDigits = minimumFractionDigitsDefault; >- intlInstance->m_maximumFractionDigits = maximumFractionDigitsDefault; >+ if (needSd || needFd) { >+ if (roundingPriority == IntlRoundingPriority::MorePrecision) >+ intlInstance->m_roundingType = IntlRoundingType::MorePrecision; >+ else if (roundingPriority == IntlRoundingPriority::LessPrecision) >+ intlInstance->m_roundingType = IntlRoundingType::LessPrecision; >+ else if (hasSd) >+ intlInstance->m_roundingType = IntlRoundingType::SignificantDigits; >+ else >+ intlInstance->m_roundingType = IntlRoundingType::FractionDigits; >+ } else { >+ intlInstance->m_roundingType = IntlRoundingType::MorePrecision; >+ intlInstance->m_minimumFractionDigits = 0; >+ intlInstance->m_maximumFractionDigits = 0; >+ intlInstance->m_minimumSignificantDigits = 1; >+ intlInstance->m_maximumSignificantDigits = 2; >+ } > } > > class IntlFieldIterator { >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp >index def029b14dd3b9eebbaa6439c4e0b4f3a46be728..bd4d1ca33cf12160a90b2115784bf46031be56ce 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp >@@ -89,23 +89,18 @@ JSC_DEFINE_HOST_FUNCTION(intlNumberFormatFuncFormat, (JSGlobalObject* globalObje > JSValue bigIntOrNumber = callFrame->argument(0).toNumeric(globalObject); > RETURN_IF_EXCEPTION(scope, encodedJSValue()); > >- scope.release(); > if (bigIntOrNumber.isNumber()) { > double value = bigIntOrNumber.asNumber(); >- return JSValue::encode(numberFormat->format(globalObject, value)); >- } >- >-#if USE(BIGINT32) >- if (bigIntOrNumber.isBigInt32()) { >- JSBigInt* value = JSBigInt::createFrom(globalObject, bigIntOrNumber.bigInt32AsInt32()); >- RETURN_IF_EXCEPTION(scope, { }); > RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, value))); > } >-#endif >+ >+ ASSERT(bigIntOrNumber.isBigInt()); >+ if (auto value = JSBigInt::tryExtractSafeDoubleInteger(bigIntOrNumber)) >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, value.value()))); > > ASSERT(bigIntOrNumber.isHeapBigInt()); > JSBigInt* value = bigIntOrNumber.asHeapBigInt(); >- return JSValue::encode(numberFormat->format(globalObject, value)); >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, value))); > } > > JSC_DEFINE_CUSTOM_GETTER(intlNumberFormatPrototypeGetterFormat, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) >diff --git a/Source/JavaScriptCore/runtime/IntlObjectInlines.h b/Source/JavaScriptCore/runtime/IntlObjectInlines.h >index 6a00ab7089cef86b6af5a84c0196e8f41246361a..41c2bfdf6ee515c234dcbce60cdb3303eab30afe 100644 >--- a/Source/JavaScriptCore/runtime/IntlObjectInlines.h >+++ b/Source/JavaScriptCore/runtime/IntlObjectInlines.h >@@ -151,6 +151,45 @@ ResultType intlOption(JSGlobalObject* globalObject, JSObject* options, PropertyN > return fallback; > } > >+template<typename ResultType> >+ResultType intlStringOrBooleanOption(JSGlobalObject* globalObject, JSObject* options, PropertyName property, ResultType trueValue, ResultType falsyValue, std::initializer_list<std::pair<ASCIILiteral, ResultType>> values, ASCIILiteral notFoundMessage, ResultType fallback) >+{ >+ // https://tc39.es/proposal-intl-numberformat-v3/out/negotiation/diff.html#sec-getstringorbooleanoption >+ >+ ASSERT(values.size() > 0); >+ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ if (!options) >+ return fallback; >+ >+ JSValue value = options->get(globalObject, property); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ if (!value.isUndefined()) { >+ if (value.isBoolean() && value.asBoolean()) >+ return trueValue; >+ >+ bool valueBoolean = value.toBoolean(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ if (!valueBoolean) >+ return falsyValue; >+ >+ String stringValue = value.toWTFString(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ for (const auto& entry : values) { >+ if (entry.first == stringValue) >+ return entry.second; >+ } >+ throwException(globalObject, scope, createRangeError(globalObject, notFoundMessage)); >+ return { }; >+ } >+ >+ return fallback; >+} > > ALWAYS_INLINE bool canUseASCIIUCADUCETComparison(UChar character) > { >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRules.cpp b/Source/JavaScriptCore/runtime/IntlPluralRules.cpp >index d8bc9669fcbac2e209ae454d62bfc3d28a35e489..d45a94c180b72bf24e11be5a9cabe715bd531235 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRules.cpp >+++ b/Source/JavaScriptCore/runtime/IntlPluralRules.cpp >@@ -32,8 +32,31 @@ > #include "JSCInlines.h" > #include "ObjectConstructor.h" > >+#ifdef U_HIDE_DRAFT_API >+#undef U_HIDE_DRAFT_API >+#endif >+#include <unicode/upluralrules.h> >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+#include <unicode/unumberrangeformatter.h> >+#endif >+#define U_HIDE_DRAFT_API 1 >+ > namespace JSC { > >+void UPluralRulesDeleter::operator()(UPluralRules* pluralRules) >+{ >+ if (pluralRules) >+ uplrules_close(pluralRules); >+} >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+void UNumberRangeFormatterDeleter::operator()(UNumberRangeFormatter* formatter) >+{ >+ if (formatter) >+ unumrf_close(formatter); >+} >+#endif >+ > const ClassInfo IntlPluralRules::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlPluralRules) }; > > using UEnumerationDeleter = ICUDeleter<uenum_close>; >@@ -107,8 +130,78 @@ void IntlPluralRules::initializePluralRules(JSGlobalObject* globalObject, JSValu > setNumberFormatDigitOptions(globalObject, this, options, 0, 3, IntlNotation::Standard); > RETURN_IF_EXCEPTION(scope, void()); > >+ auto locale = m_locale.utf8(); > UErrorCode status = U_ZERO_ERROR; >- m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, m_locale.utf8().data(), nullptr, &status)); >+ >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ StringBuilder skeletonBuilder; >+ skeletonBuilder.append("rounding-mode-half-up"); >+ >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#integer-width >+ skeletonBuilder.append(" integer-width/", WTF::ICU::majorVersion() >= 67 ? '*' : '+'); // Prior to ICU 67, use the symbol + instead of *. >+ for (unsigned i = 0; i < m_minimumIntegerDigits; ++i) >+ skeletonBuilder.append('0'); >+ >+ switch (m_roundingType) { >+ case IntlRoundingType::FractionDigits: { >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision >+ skeletonBuilder.append(" ."); >+ for (unsigned i = 0; i < m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('0'); >+ for (unsigned i = 0; i < m_maximumFractionDigits - m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('#'); >+ break; >+ } >+ case IntlRoundingType::SignificantDigits: { >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision >+ skeletonBuilder.append(' '); >+ for (unsigned i = 0; i < m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('@'); >+ for (unsigned i = 0; i < m_maximumSignificantDigits - m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('#'); >+ break; >+ } >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: >+ // Before Intl.NumberFormat v3, it was CompactRounding mode, where we do not configure anything. >+ // So, if linked ICU is ~68, we do nothing. >+ if (WTF::ICU::majorVersion() >= 69) { >+ // https://github.com/unicode-org/icu/commit/d7db6c1f8655bb53153695b09a50029fd04a8364 >+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#precision >+ skeletonBuilder.append(" ."); >+ for (unsigned i = 0; i < m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('0'); >+ for (unsigned i = 0; i < m_maximumFractionDigits - m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('#'); >+ skeletonBuilder.append('/'); >+ for (unsigned i = 0; i < m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('@'); >+ for (unsigned i = 0; i < m_maximumSignificantDigits - m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('#'); >+ skeletonBuilder.append(m_roundingType == IntlRoundingType::MorePrecision ? 'r' : 's'); >+ } >+ break; >+ } >+ >+ String skeleton = skeletonBuilder.toString(); >+ StringView skeletonView(skeleton); >+ auto upconverted = skeletonView.upconvertedCharacters(); >+ >+ m_numberFormatter = std::unique_ptr<UNumberFormatter, ICUDeleter<unumf_close>>(unumf_openForSkeletonAndLocale(upconverted.get(), skeletonView.length(), locale.data(), &status)); >+ if (U_FAILURE(status)) { >+ throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); >+ return; >+ } >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ m_numberRangeFormatter = std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter>(unumrf_openForSkeletonWithCollapseAndIdentityFallback(upconverted.get(), skeletonView.length(), UNUM_RANGE_COLLAPSE_NONE, UNUM_IDENTITY_FALLBACK_RANGE, locale.data(), nullptr, &status)); >+ if (U_FAILURE(status)) { >+ throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); >+ return; >+ } >+#endif >+#else >+ m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, locale.data(), nullptr, &status)); > if (U_FAILURE(status)) { > throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); > return; >@@ -125,13 +218,13 @@ void IntlPluralRules::initializePluralRules(JSGlobalObject* globalObject, JSValu > unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits); > unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits); > break; >- default: >- RELEASE_ASSERT_NOT_REACHED(); >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: > break; > } >+#endif > >- status = U_ZERO_ERROR; >- m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(m_locale.utf8().data(), m_type == Type::Ordinal ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL, &status)); >+ m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(locale.data(), m_type == Type::Ordinal ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL, &status)); > if (U_FAILURE(status)) { > throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); > return; >@@ -159,8 +252,12 @@ JSObject* IntlPluralRules::resolvedOptions(JSGlobalObject* globalObject) const > options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); > options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; >- default: >- RELEASE_ASSERT_NOT_REACHED(); >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: >+ options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; > } > >@@ -183,8 +280,9 @@ JSObject* IntlPluralRules::resolvedOptions(JSGlobalObject* globalObject) const > RETURN_IF_EXCEPTION(scope, { }); > } > options->putDirect(vm, Identifier::fromString(vm, "pluralCategories"), categories); >+ options->putDirect(vm, vm.propertyNames->roundingMode, jsNontrivialString(vm, IntlNumberFormat::roundingPriorityString(m_roundingType))); > >- RELEASE_AND_RETURN(scope, options); >+ return options; > } > > // https://tc39.es/ecma402/#sec-resolveplural >@@ -199,12 +297,57 @@ JSValue IntlPluralRules::select(JSGlobalObject* globalObject, double value) cons > return jsNontrivialString(vm, "other"_s); > > UErrorCode status = U_ZERO_ERROR; >+ >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ unumf_formatDouble(m_numberFormatter.get(), value, formattedNumber.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ Vector<UChar, 32> buffer; >+ status = callBufferProducingFunction(uplrules_selectFormatted, m_pluralRules.get(), formattedNumber.get(), buffer); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ return jsString(vm, String(buffer)); >+#else > Vector<UChar, 8> result(8); > auto length = uplrules_selectWithFormat(m_pluralRules.get(), value, m_numberFormat.get(), result.data(), result.size(), &status); > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "failed to select plural value"_s); > > return jsString(vm, String(result.data(), length)); >+#endif >+} >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+JSValue IntlPluralRules::selectRange(JSGlobalObject* globalObject, double start, double end) const >+{ >+ ASSERT(m_numberRangeFormatter); >+ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ if (start > end) { >+ throwRangeError(globalObject, scope, "start is larger than end"_s); >+ return { }; >+ } >+ >+ UErrorCode status = U_ZERO_ERROR; >+ auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select range of plural value"_s); >+ >+ unumrf_formatDoubleRange(m_numberRangeFormatter.get(), start, end, range.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select range of plural value"_s); >+ >+ Vector<UChar, 32> buffer; >+ status = callBufferProducingFunction(uplrules_selectForRange, m_pluralRules.get(), range.get(), buffer); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ return jsString(vm, String(buffer)); > } >+#endif > > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRules.h b/Source/JavaScriptCore/runtime/IntlPluralRules.h >index 6d3acab84e814e5f7c07d024f3e25ff2e5f0f6e4..5f9640199931362ee590f11ad57f66d64e88d502 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRules.h >+++ b/Source/JavaScriptCore/runtime/IntlPluralRules.h >@@ -28,11 +28,28 @@ > > #include "IntlNumberFormat.h" > #include <unicode/unum.h> >-#include <unicode/upluralrules.h> > #include <wtf/unicode/icu/ICUHelpers.h> > >+#if !defined(HAVE_ICU_U_NUMBER_RANGE_FORMATTER) >+#if U_ICU_VERSION_MAJOR_NUM >= 68 >+#define HAVE_ICU_U_NUMBER_RANGE_FORMATTER 1 >+#endif >+#endif >+ >+struct UPluralRules; >+struct UNumberRangeFormatter; >+ > namespace JSC { > >+struct UPluralRulesDeleter { >+ JS_EXPORT_PRIVATE void operator()(UPluralRules*); >+}; >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+struct UNumberRangeFormatterDeleter { >+ JS_EXPORT_PRIVATE void operator()(UNumberRangeFormatter*); >+}; >+#endif >+ > enum class RelevantExtensionKey : uint8_t; > > class IntlPluralRules final : public JSNonFinalObject { >@@ -64,6 +81,10 @@ class IntlPluralRules final : public JSNonFinalObject { > JSValue select(JSGlobalObject*, double value) const; > JSObject* resolvedOptions(JSGlobalObject*) const; > >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ JSValue selectRange(JSGlobalObject*, double start, double end) const; >+#endif >+ > private: > IntlPluralRules(VM&, Structure*); > void finishCreation(VM&); >@@ -73,11 +94,18 @@ class IntlPluralRules final : public JSNonFinalObject { > > enum class Type : bool { Cardinal, Ordinal }; > >- using UPluralRulesDeleter = ICUDeleter<uplrules_close>; > using UNumberFormatDeleter = ICUDeleter<unum_close>; > > std::unique_ptr<UPluralRules, UPluralRulesDeleter> m_pluralRules; >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ using UNumberFormatterDeleter = ICUDeleter<unumf_close>; >+ std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter> m_numberFormatter; >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter> m_numberRangeFormatter; >+#endif >+#else > std::unique_ptr<UNumberFormat, UNumberFormatDeleter> m_numberFormat; >+#endif > > String m_locale; > unsigned m_minimumIntegerDigits { 1 }; >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp >index c80bfa2223fd50e8b97818ced4b0758bbcde6c27..ec69fe29bb23980c647ba4af064b1868716a9a8b 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp >+++ b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp >@@ -33,6 +33,9 @@ > namespace JSC { > > static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect); >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange); >+#endif > static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions); > > } >@@ -50,10 +53,10 @@ const ClassInfo IntlPluralRulesPrototype::s_info = { "Intl.PluralRules", &Base:: > @end > */ > >-IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject*, Structure* structure) >+IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) > { > IntlPluralRulesPrototype* object = new (NotNull, allocateCell<IntlPluralRulesPrototype>(vm.heap)) IntlPluralRulesPrototype(vm, structure); >- object->finishCreation(vm); >+ object->finishCreation(vm, globalObject); > return object; > } > >@@ -67,11 +70,15 @@ IntlPluralRulesPrototype::IntlPluralRulesPrototype(VM& vm, Structure* structure) > { > } > >-void IntlPluralRulesPrototype::finishCreation(VM& vm) >+void IntlPluralRulesPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) > { > Base::finishCreation(vm); > ASSERT(inherits(vm, info())); > JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); >+ UNUSED_PARAM(globalObject); >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->selectRange, intlPluralRulesPrototypeFuncSelectRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 0); >+#endif > } > > JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect, (JSGlobalObject* globalObject, CallFrame* callFrame)) >@@ -92,6 +99,27 @@ JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect, (JSGlobalObject* gl > RELEASE_AND_RETURN(scope, JSValue::encode(pluralRules->select(globalObject, value))); > } > >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange, (JSGlobalObject* globalObject, CallFrame* callFrame)) >+{ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ // https://tc39.es/proposal-intl-numberformat-v3/out/pluralrules/diff.html#sec-intl.pluralrules.prototype.selectrange >+ IntlPluralRules* pluralRules = jsDynamicCast<IntlPluralRules*>(vm, callFrame->thisValue()); >+ if (!pluralRules) >+ return JSValue::encode(throwTypeError(globalObject, scope, "Intl.PluralRules.prototype.selectRange called on value that's not a PluralRules"_s)); >+ >+ double start = callFrame->argument(0).toNumber(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ double end = callFrame->argument(1).toNumber(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ RELEASE_AND_RETURN(scope, JSValue::encode(pluralRules->selectRange(globalObject, start, end))); >+} >+#endif >+ > JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame)) > { > VM& vm = globalObject->vm(); >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h >index 6faa3a4f78aea598432cb13037f103593625084b..61e08b2f4d2cab063c75c6976c1b360fafe5fd0f 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h >+++ b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h >@@ -49,7 +49,7 @@ class IntlPluralRulesPrototype final : public JSNonFinalObject { > > private: > IntlPluralRulesPrototype(VM&, Structure*); >- void finishCreation(VM&); >+ void finishCreation(VM&, JSGlobalObject*); > }; > > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/JSBigInt.h b/Source/JavaScriptCore/runtime/JSBigInt.h >index 8961c1d8fb6101ab68d970f02b71edcb334dfd78..3c47194537578ab118c624d69c5540224e84179c 100644 >--- a/Source/JavaScriptCore/runtime/JSBigInt.h >+++ b/Source/JavaScriptCore/runtime/JSBigInt.h >@@ -462,6 +462,8 @@ class JSBigInt final : public JSCell { > return hashSlow(); > } > >+ static std::optional<double> tryExtractSafeDoubleInteger(JSValue bigIntValue); >+ > private: > JSBigInt(VM&, Structure*, Digit*, unsigned length); > >@@ -676,4 +678,36 @@ ALWAYS_INLINE JSValue tryConvertToBigInt32(JSBigInt* bigInt) > return bigInt; > } > >+ALWAYS_INLINE std::optional<double> JSBigInt::tryExtractSafeDoubleInteger(JSValue bigIntValue) >+{ >+ ASSERT(bigIntValue.isBigInt()); >+#if USE(BIGINT32) >+ if (bigIntValue.isBigInt32()) >+ return bigIntValue.bigInt32AsInt32(); >+#endif >+ ASSERT(bigIntValue.isHeapBigInt()); >+ JSBigInt* bigInt = bigIntValue.asHeapBigInt(); >+ if (!bigInt->length()) >+ return 0; >+ >+ uint64_t value = 0; >+ if constexpr (sizeof(Digit) == 8) { >+ if (bigInt->length() != 1) >+ return std::nullopt; >+ value = bigInt->digit(0); >+ } else { >+ ASSERT(sizeof(Digit) == 4); >+ if (bigInt->length() > 2) >+ return std::nullopt; >+ uint64_t value = bigInt->digit(0); >+ if (bigInt->length() == 2) >+ value |= (static_cast<uint64_t>(bigInt->digit(1)) << 32); >+ } >+ >+ if (value <= static_cast<uint64_t>(maxSafeInteger())) >+ return (bigInt->sign()) ? -static_cast<double>(value) : static_cast<double>(value); >+ >+ return std::nullopt; >+} >+ > } // namespace JSC >diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog >index acc804eebfbbfd29f3c63cac4366a060f6fcb621..4913320f0f0cba668bb0d94671874ebb1cb50d7b 100644 >--- a/JSTests/ChangeLog >+++ b/JSTests/ChangeLog >@@ -1,3 +1,15 @@ >+2021-10-24 Yusuke Suzuki <ysuzuki@apple.com> >+ >+ [JSC] Implement IntlNumberFormat v3 >+ https://bugs.webkit.org/show_bug.cgi?id=215438 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * stress/intl-numberformat.js: >+ (testNumberFormat): >+ (shouldBe.testNumberFormat.Intl.NumberFormat): >+ * stress/intl-pluralrules.js: >+ > 2021-10-23 Phillip Mates <pmates@igalia.com> > > update test262 >diff --git a/JSTests/stress/intl-numberformat.js b/JSTests/stress/intl-numberformat.js >index 8539c93b5acad66f0aebcbfd558a5e5310264bc5..3c301f578a5db9317411621900fd4d71a5a655d5 100644 >--- a/JSTests/stress/intl-numberformat.js >+++ b/JSTests/stress/intl-numberformat.js >@@ -53,9 +53,13 @@ function testNumberFormat(numberFormat, possibleDifferences) { > maximumFractionDigits: 3, > minimumSignificantDigits: undefined, > maximumSignificantDigits: undefined, >- useGrouping: true, >+ useGrouping: "auto", > notation: "standard", >- signDisplay: "auto" >+ signDisplay: "auto", >+ roundingMode: "halfExpand", >+ roundingIncrement: 1, >+ trailingZeroDisplay: "auto", >+ roundingPriority: "auto", > }; > Object.assign(defaultOptions, difference); > return JSON.stringify(defaultOptions); >@@ -184,9 +188,9 @@ function testNumberFormat(numberFormat, possibleDifferences) { > shouldThrow(() => Intl.NumberFormat('en', {maximumSignificantDigits: 22}), RangeError); > > // The option useGrouping is processed correctly. >-shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: true}]), true); >+shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: "always"}]), true); > shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: false}), [{locale: 'en', useGrouping: false}]), true); >-shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'false'}), [{locale: 'en', useGrouping: true}]), true); >+shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'min2'}), [{locale: 'en', useGrouping: "min2"}]), true); > shouldThrow(() => Intl.NumberFormat('en', { get useGrouping() { throw new Error(); } }), Error); > > // 11.2 Properties of the Intl.NumberFormat Constructor >@@ -435,7 +439,7 @@ function testNumberFormat(numberFormat, possibleDifferences) { > { > let options = defaultNFormat.resolvedOptions(); > delete options.locale; >- shouldBe(JSON.stringify(options), '{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":true,"notation":"standard","signDisplay":"auto"}'); >+ shouldBe(JSON.stringify(options), `{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":"auto","notation":"standard","signDisplay":"auto","roundingMode":"halfExpand","roundingIncrement":1,"trailingZeroDisplay":"auto","roundingPriority":"auto"}`); > } > > // Legacy compatibility with ECMA-402 1.0 >diff --git a/JSTests/stress/intl-pluralrules.js b/JSTests/stress/intl-pluralrules.js >index a438eace1d6647a09d41656267121ccd48505e07..d32773e2ae6f7475784e7dad220476178c2c37ff 100644 >--- a/JSTests/stress/intl-pluralrules.js >+++ b/JSTests/stress/intl-pluralrules.js >@@ -294,3 +294,8 @@ function shouldThrow(func, errorType) { > shouldBe(new Intl.PluralRules('ar').resolvedOptions().pluralCategories.join(), 'few,many,one,two,zero,other'); > shouldBe(new Intl.PluralRules('en').resolvedOptions().pluralCategories.join(), 'one,other'); > shouldBe(new Intl.PluralRules('en', {type: 'ordinal'}).resolvedOptions().pluralCategories.join(), 'few,one,two,other'); >+ >+if (Intl.PluralRules.prototype.selectRange) { >+ const pl = new Intl.PluralRules("sl"); >+ shouldBe(pl.selectRange(102, 201), `few`); >+}
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 215438
:
436468
|
442293
|
442294
|
442295
|
442296
|
442297
|
442298
|
442299
|
442300
|
442302
|
442304
|
442305
|
442464
|
442472
|
442476
|
442502
|
442511
|
442540
|
442549
|
442558