diff --git a/icu4c/source/common/unicode/utypes.h b/icu4c/source/common/unicode/utypes.h index 0151ebd47015..52cef75c9e40 100644 --- a/icu4c/source/common/unicode/utypes.h +++ b/icu4c/source/common/unicode/utypes.h @@ -596,14 +596,15 @@ typedef enum UErrorCode { U_MF_SELECTOR_ERROR, /**< A selector function is applied to an operand of the wrong type @internal ICU 75 technology preview @deprecated This API is for technology preview only. */ U_MF_MISSING_SELECTOR_ANNOTATION_ERROR, /**< A selector expression evaluates to an unannotated operand. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */ U_MF_DUPLICATE_DECLARATION_ERROR, /**< The same variable is declared in more than one .local or .input declaration. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */ - U_MF_OPERAND_MISMATCH_ERROR, /**< An operand provided to a function does not have the required form for that function @internal ICU 75 technology preview @deprecated This API is for technology preview only. */ - U_MF_DUPLICATE_VARIANT_ERROR, /**< A message includes a variant with the same key list as another variant. @internal ICU 76 technology preview @deprecated This API is for technology preview only. */ + U_MF_OPERAND_MISMATCH_ERROR, /**< An operand provided to a function does not have the required form for that function @internal ICU 75 technology preview @deprecated This API is for technology preview only. */ + U_MF_DUPLICATE_VARIANT_ERROR, /**< A message includes a variant with the same key list as another variant. @internal ICU 76 technology preview @deprecated This API is for technology preview only. */ + U_MF_BAD_OPTION_ERROR, /**< A function is called with an option that the function's implementation does not support. @internal ICU 77 technology preview @deprecated This API is for technology preview only. */ #ifndef U_HIDE_DEPRECATED_API /** * One more than the highest normal formatting API error code. * @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420. */ - U_FMT_PARSE_ERROR_LIMIT = 0x10120, + U_FMT_PARSE_ERROR_LIMIT = 0x10121, #endif // U_HIDE_DEPRECATED_API /* diff --git a/icu4c/source/common/utypes.cpp b/icu4c/source/common/utypes.cpp index 4602314147f1..1fad01d2e45d 100644 --- a/icu4c/source/common/utypes.cpp +++ b/icu4c/source/common/utypes.cpp @@ -140,7 +140,8 @@ _uFmtErrorName[U_FMT_PARSE_ERROR_LIMIT - U_FMT_PARSE_ERROR_START] = { "U_MF_MISSING_SELECTOR_ANNOTATION_ERROR", "U_MF_DUPLICATE_DECLARATION_ERROR", "U_MF_OPERAND_MISMATCH_ERROR", - "U_MF_DUPLICATE_VARIANT_ERROR" + "U_MF_DUPLICATE_VARIANT_ERROR", + "U_MF_BAD_OPTION_ERROR" }; static const char * const diff --git a/icu4c/source/i18n/messageformat2.cpp b/icu4c/source/i18n/messageformat2.cpp index 73f7fa45e69f..6dadd3d0b2b3 100644 --- a/icu4c/source/i18n/messageformat2.cpp +++ b/icu4c/source/i18n/messageformat2.cpp @@ -214,6 +214,9 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O if (status == U_MF_OPERAND_MISMATCH_ERROR) { status = U_ZERO_ERROR; errs.setOperandMismatchError(functionName, status); + } else if (status == U_MF_BAD_OPTION_ERROR) { + status = U_ZERO_ERROR; + errs.setBadOptionError(functionName, status); } else { status = U_ZERO_ERROR; // Convey any error generated by the formatter diff --git a/icu4c/source/i18n/messageformat2_errors.cpp b/icu4c/source/i18n/messageformat2_errors.cpp index 9d1d6bab81a1..77274b181dd2 100644 --- a/icu4c/source/i18n/messageformat2_errors.cpp +++ b/icu4c/source/i18n/messageformat2_errors.cpp @@ -27,6 +27,10 @@ namespace message2 { addError(DynamicError(DynamicErrorType::FormattingError, UnicodeString("unknown formatter")), status); } + void DynamicErrors::setBadOptionError(const FunctionName& functionName, UErrorCode& status) { + addError(DynamicError(DynamicErrorType::BadOptionError, functionName), status); + } + void DynamicErrors::setOperandMismatchError(const FunctionName& formatterName, UErrorCode& status) { addError(DynamicError(DynamicErrorType::OperandMismatchError, formatterName), status); } @@ -121,29 +125,27 @@ namespace message2 { if (U_FAILURE(status)) { return; } - U_ASSERT(resolutionAndFormattingErrors->size() > 0); - switch (first().type) { - case DynamicErrorType::UnknownFunction: { - status = U_MF_UNKNOWN_FUNCTION_ERROR; - break; - } - case DynamicErrorType::UnresolvedVariable: { - status = U_MF_UNRESOLVED_VARIABLE_ERROR; - break; - } - case DynamicErrorType::FormattingError: { - status = U_MF_FORMATTING_ERROR; - break; - } - case DynamicErrorType::OperandMismatchError: { - status = U_MF_OPERAND_MISMATCH_ERROR; - break; - } - case DynamicErrorType::SelectorError: { - status = U_MF_SELECTOR_ERROR; - break; - } - } + U_ASSERT(resolutionAndFormattingErrors->size() > 0); + switch (first().type) { + case DynamicErrorType::UnknownFunction: + status = U_MF_UNKNOWN_FUNCTION_ERROR; + break; + case DynamicErrorType::UnresolvedVariable: + status = U_MF_UNRESOLVED_VARIABLE_ERROR; + break; + case DynamicErrorType::FormattingError: + status = U_MF_FORMATTING_ERROR; + break; + case DynamicErrorType::OperandMismatchError: + status = U_MF_OPERAND_MISMATCH_ERROR; + break; + case DynamicErrorType::BadOptionError: + status = U_MF_BAD_OPTION_ERROR; + break; + case DynamicErrorType::SelectorError: + status = U_MF_SELECTOR_ERROR; + break; + } } void StaticErrors::addSyntaxError(UErrorCode& status) { @@ -199,34 +201,24 @@ namespace message2 { void* errorP = static_cast(create(std::move(e), status)); U_ASSERT(resolutionAndFormattingErrors.isValid()); + resolutionAndFormattingErrors->adoptElement(errorP, status); switch (type) { - case DynamicErrorType::UnresolvedVariable: { + case DynamicErrorType::UnresolvedVariable: unresolvedVariableError = true; - resolutionAndFormattingErrors->adoptElement(errorP, status); break; - } - case DynamicErrorType::FormattingError: { - formattingError = true; - resolutionAndFormattingErrors->adoptElement(errorP, status); - break; - } - case DynamicErrorType::OperandMismatchError: { + case DynamicErrorType::FormattingError: + case DynamicErrorType::OperandMismatchError: + case DynamicErrorType::BadOptionError: formattingError = true; - resolutionAndFormattingErrors->adoptElement(errorP, status); break; - } - case DynamicErrorType::SelectorError: { + case DynamicErrorType::SelectorError: selectorError = true; - resolutionAndFormattingErrors->adoptElement(errorP, status); break; - } - case DynamicErrorType::UnknownFunction: { + case DynamicErrorType::UnknownFunction: unknownFunctionError = true; - resolutionAndFormattingErrors->adoptElement(errorP, status); break; } - } } void StaticErrors::checkErrors(UErrorCode& status) const { diff --git a/icu4c/source/i18n/messageformat2_errors.h b/icu4c/source/i18n/messageformat2_errors.h index f84aa7362837..d24cfdaebc50 100644 --- a/icu4c/source/i18n/messageformat2_errors.h +++ b/icu4c/source/i18n/messageformat2_errors.h @@ -65,6 +65,7 @@ namespace message2 { UnresolvedVariable, FormattingError, OperandMismatchError, + BadOptionError, SelectorError, UnknownFunction, }; @@ -126,6 +127,7 @@ namespace message2 { void setFormattingError(const FunctionName&, UErrorCode&); // Used when the name of the offending formatter is unknown void setFormattingError(UErrorCode&); + void setBadOptionError(const FunctionName&, UErrorCode&); void setOperandMismatchError(const FunctionName&, UErrorCode&); bool hasDataModelError() const { return staticErrors.hasDataModelError(); } bool hasFormattingError() const { return formattingError; } diff --git a/icu4c/source/i18n/messageformat2_function_registry.cpp b/icu4c/source/i18n/messageformat2_function_registry.cpp index 17955760ecfb..a9a50264da26 100644 --- a/icu4c/source/i18n/messageformat2_function_registry.cpp +++ b/icu4c/source/i18n/messageformat2_function_registry.cpp @@ -302,11 +302,15 @@ MFFunctionRegistry::~MFFunctionRegistry() { } } - int32_t maxSignificantDigits = number.maximumSignificantDigits(opts); + int32_t maxSignificantDigits = number.maximumSignificantDigits(opts, status); if (!isInteger) { - int32_t minFractionDigits = number.minimumFractionDigits(opts); - int32_t maxFractionDigits = number.maximumFractionDigits(opts); - int32_t minSignificantDigits = number.minimumSignificantDigits(opts); + int32_t minFractionDigits = number.minimumFractionDigits(opts, status); + int32_t maxFractionDigits = number.maximumFractionDigits(opts, status); + int32_t minSignificantDigits = number.minimumSignificantDigits(opts, status); + if (U_FAILURE(status)) { + status = U_MF_BAD_OPTION_ERROR; + return nf.locale(number.locale); + } Precision p = Precision::unlimited(); bool precisionOptions = false; @@ -343,8 +347,14 @@ MFFunctionRegistry::~MFFunctionRegistry() { } // All other options apply to both `:number` and `:integer` - int32_t minIntegerDigits = number.minimumIntegerDigits(opts); - nf = nf.integerWidth(IntegerWidth::zeroFillTo(minIntegerDigits)); + int32_t minIntegerDigits = number.minimumIntegerDigits(opts, status); + if (U_FAILURE(status)) { + status = U_MF_BAD_OPTION_ERROR; + return nf.locale(number.locale); + } + if (minIntegerDigits != -1) { + nf = nf.integerWidth(IntegerWidth::zeroFillTo(minIntegerDigits)); + } // signDisplay UnicodeString sd = opts.getStringFunctionOption(UnicodeString("signDisplay")); @@ -478,68 +488,53 @@ static FormattedPlaceholder tryParsingNumberLiteral(const number::LocalizedNumbe return FormattedPlaceholder(input, FormattedValue(std::move(result))); } -int32_t StandardFunctions::Number::maximumFractionDigits(const FunctionOptions& opts) const { +int32_t StandardFunctions::Number::digitSizeOption(const FunctionOptions& opts, + const UnicodeString& optionName, + int32_t defaultVal, + UErrorCode& status) const { + Formattable opt; + + if (opts.getFunctionOption(optionName, opt)) { + int32_t result = static_cast(getInt64Value(locale, opt, status)); + // Option value must be a non-negative integer + if (U_SUCCESS(status) && result < 0) { + status = U_MF_BAD_OPTION_ERROR; + } + return result; + } + return defaultVal; +} + +int32_t StandardFunctions::Number::maximumFractionDigits(const FunctionOptions& opts, UErrorCode& status) const { Formattable opt; if (isInteger) { return 0; } - if (opts.getFunctionOption(UnicodeString("maximumFractionDigits"), opt)) { - UErrorCode localErrorCode = U_ZERO_ERROR; - int64_t val = getInt64Value(locale, opt, localErrorCode); - if (U_SUCCESS(localErrorCode)) { - return static_cast(val); - } - } - // Returning -1 indicates that the option wasn't provided or was a non-integer. - // The caller needs to check for that case, since passing -1 to Precision::maxFraction() - // is an error. - return -1; + return digitSizeOption(opts, UnicodeString("maximumFractionDigits"), -1, status); } -int32_t StandardFunctions::Number::minimumFractionDigits(const FunctionOptions& opts) const { +int32_t StandardFunctions::Number::minimumFractionDigits(const FunctionOptions& opts, UErrorCode& status) const { Formattable opt; - if (!isInteger) { - if (opts.getFunctionOption(UnicodeString("minimumFractionDigits"), opt)) { - UErrorCode localErrorCode = U_ZERO_ERROR; - int64_t val = getInt64Value(locale, opt, localErrorCode); - if (U_SUCCESS(localErrorCode)) { - return static_cast(val); - } - } + if (isInteger) { + return 0; } - // Returning -1 indicates that the option wasn't provided or was a non-integer. - // The caller needs to check for that case, since passing -1 to Precision::minFraction() - // is an error. - return -1; + return digitSizeOption(opts, UnicodeString("minimumFractionDigits"), -1, status); } -int32_t StandardFunctions::Number::minimumIntegerDigits(const FunctionOptions& opts) const { +int32_t StandardFunctions::Number::minimumIntegerDigits(const FunctionOptions& opts, UErrorCode& status) const { Formattable opt; - if (opts.getFunctionOption(UnicodeString("minimumIntegerDigits"), opt)) { - UErrorCode localErrorCode = U_ZERO_ERROR; - int64_t val = getInt64Value(locale, opt, localErrorCode); - if (U_SUCCESS(localErrorCode)) { - return static_cast(val); - } - } - return 0; + return digitSizeOption(opts, UnicodeString("minimumIntegerDigits"), -1, status); } -int32_t StandardFunctions::Number::minimumSignificantDigits(const FunctionOptions& opts) const { +int32_t StandardFunctions::Number::minimumSignificantDigits(const FunctionOptions& opts, UErrorCode& status) const { Formattable opt; if (!isInteger) { - if (opts.getFunctionOption(UnicodeString("minimumSignificantDigits"), opt)) { - UErrorCode localErrorCode = U_ZERO_ERROR; - int64_t val = getInt64Value(locale, opt, localErrorCode); - if (U_SUCCESS(localErrorCode)) { - return static_cast(val); - } - } + return digitSizeOption(opts, UnicodeString("minimumSignificantDigits"), -1, status); } // Returning -1 indicates that the option wasn't provided or was a non-integer. // The caller needs to check for that case, since passing -1 to Precision::minSignificantDigits() @@ -547,20 +542,13 @@ int32_t StandardFunctions::Number::minimumSignificantDigits(const FunctionOption return -1; } -int32_t StandardFunctions::Number::maximumSignificantDigits(const FunctionOptions& opts) const { +int32_t StandardFunctions::Number::maximumSignificantDigits(const FunctionOptions& opts, UErrorCode& status) const { Formattable opt; - if (opts.getFunctionOption(UnicodeString("maximumSignificantDigits"), opt)) { - UErrorCode localErrorCode = U_ZERO_ERROR; - int64_t val = getInt64Value(locale, opt, localErrorCode); - if (U_SUCCESS(localErrorCode)) { - return static_cast(val); - } - } - // Returning -1 indicates that the option wasn't provided or was a non-integer. - // The caller needs to check for that case, since passing -1 to Precision::maxSignificantDigits() + // Returning 0 indicates that the option wasn't provided. + // The caller needs to check for that case, since passing 0 to Precision::maxSignificantDigits() // is an error. - return -1; // Not a valid value for Precision; has to be checked + return digitSizeOption(opts, UnicodeString("maximumSignificantDigits"), -1, status); } bool StandardFunctions::Number::usePercent(const FunctionOptions& opts) const { @@ -593,6 +581,9 @@ FormattedPlaceholder StandardFunctions::Number::format(FormattedPlaceholder&& ar number::LocalizedNumberFormatter realFormatter; realFormatter = formatterForOptions(*this, opts, errorCode); + if (U_FAILURE(errorCode)) { + return {}; + } number::FormattedNumber numberResult; if (U_SUCCESS(errorCode)) { @@ -967,6 +958,7 @@ FormattedPlaceholder StandardFunctions::DateTime::format(FormattedPlaceholder&& bool hasDateStyleOption = opts.getFunctionOption(dateStyleName, opt); bool hasTimeStyleOption = opts.getFunctionOption(timeStyleName, opt); bool noOptions = opts.optionsCount() == 0; + bool fieldOptions = false; bool useStyle = (type == DateTimeFactory::DateTimeType::DateTime && (hasDateStyleOption || hasTimeStyleOption @@ -1004,114 +996,128 @@ FormattedPlaceholder StandardFunctions::DateTime::format(FormattedPlaceholder&& timeStyle = stringToStyle(getFunctionOption(toFormat, opts, styleName), errorCode); df.adoptInstead(DateFormat::createTimeInstance(timeStyle, locale)); } - } else { - // Build up a skeleton based on the field options, then use that to - // create the date formatter + } + // Build up a skeleton based on the field options, then use that to + // create the date formatter - UnicodeString skeleton; - #define ADD_PATTERN(s) skeleton += UnicodeString(s) - if (U_SUCCESS(errorCode)) { - // Year - UnicodeString year = getFunctionOption(toFormat, opts, UnicodeString("year"), errorCode); - if (U_FAILURE(errorCode)) { - errorCode = U_ZERO_ERROR; - } else { - useDate = true; - if (year == UnicodeString("2-digit")) { - ADD_PATTERN("YY"); - } else if (year == UnicodeString("numeric")) { - ADD_PATTERN("YYYY"); - } - } - // Month - UnicodeString month = getFunctionOption(toFormat, opts, UnicodeString("month"), errorCode); - if (U_FAILURE(errorCode)) { - errorCode = U_ZERO_ERROR; - } else { - useDate = true; - /* numeric, 2-digit, long, short, narrow */ - if (month == UnicodeString("long")) { - ADD_PATTERN("MMMM"); - } else if (month == UnicodeString("short")) { - ADD_PATTERN("MMM"); - } else if (month == UnicodeString("narrow")) { - ADD_PATTERN("MMMMM"); - } else if (month == UnicodeString("numeric")) { - ADD_PATTERN("M"); - } else if (month == UnicodeString("2-digit")) { - ADD_PATTERN("MM"); - } + UnicodeString skeleton; +#define ADD_PATTERN(s) skeleton += UnicodeString(s) + if (U_SUCCESS(errorCode)) { + // Year + UnicodeString year = getFunctionOption(toFormat, opts, UnicodeString("year"), errorCode); + if (U_FAILURE(errorCode)) { + errorCode = U_ZERO_ERROR; + } else { + fieldOptions = true; + useDate = true; + if (year == UnicodeString("2-digit")) { + ADD_PATTERN("YY"); + } else if (year == UnicodeString("numeric")) { + ADD_PATTERN("YYYY"); } - // Weekday - UnicodeString weekday = getFunctionOption(toFormat, opts, UnicodeString("weekday"), errorCode); - if (U_FAILURE(errorCode)) { - errorCode = U_ZERO_ERROR; - } else { - useDate = true; - if (weekday == UnicodeString("long")) { - ADD_PATTERN("EEEE"); - } else if (weekday == UnicodeString("short")) { - ADD_PATTERN("EEEEE"); - } else if (weekday == UnicodeString("narrow")) { - ADD_PATTERN("EEEEE"); - } + } + // Month + UnicodeString month = getFunctionOption(toFormat, opts, UnicodeString("month"), errorCode); + if (U_FAILURE(errorCode)) { + errorCode = U_ZERO_ERROR; + } else { + fieldOptions = true; + useDate = true; + /* numeric, 2-digit, long, short, narrow */ + if (month == UnicodeString("long")) { + ADD_PATTERN("MMMM"); + } else if (month == UnicodeString("short")) { + ADD_PATTERN("MMM"); + } else if (month == UnicodeString("narrow")) { + ADD_PATTERN("MMMMM"); + } else if (month == UnicodeString("numeric")) { + ADD_PATTERN("M"); + } else if (month == UnicodeString("2-digit")) { + ADD_PATTERN("MM"); } - // Day - UnicodeString day = getFunctionOption(toFormat, opts, UnicodeString("day"), errorCode); - if (U_FAILURE(errorCode)) { - errorCode = U_ZERO_ERROR; - } else { - useDate = true; - if (day == UnicodeString("numeric")) { - ADD_PATTERN("d"); - } else if (day == UnicodeString("2-digit")) { - ADD_PATTERN("dd"); + } + // Weekday + UnicodeString weekday = getFunctionOption(toFormat, opts, UnicodeString("weekday"), errorCode); + if (U_FAILURE(errorCode)) { + errorCode = U_ZERO_ERROR; + } else { + fieldOptions = true; + useDate = true; + if (weekday == UnicodeString("long")) { + ADD_PATTERN("EEEE"); + } else if (weekday == UnicodeString("short")) { + ADD_PATTERN("EEEEE"); + } else if (weekday == UnicodeString("narrow")) { + ADD_PATTERN("EEEEE"); } + } + // Day + UnicodeString day = getFunctionOption(toFormat, opts, UnicodeString("day"), errorCode); + if (U_FAILURE(errorCode)) { + errorCode = U_ZERO_ERROR; + } else { + fieldOptions = true; + useDate = true; + if (day == UnicodeString("numeric")) { + ADD_PATTERN("d"); + } else if (day == UnicodeString("2-digit")) { + ADD_PATTERN("dd"); } - // Hour - UnicodeString hour = getFunctionOption(toFormat, opts, UnicodeString("hour"), errorCode); - if (U_FAILURE(errorCode)) { - errorCode = U_ZERO_ERROR; - } else { - useTime = true; - if (hour == UnicodeString("numeric")) { - ADD_PATTERN("h"); - } else if (hour == UnicodeString("2-digit")) { - ADD_PATTERN("hh"); - } + } + // Hour + UnicodeString hour = getFunctionOption(toFormat, opts, UnicodeString("hour"), errorCode); + if (U_FAILURE(errorCode)) { + errorCode = U_ZERO_ERROR; + } else { + fieldOptions = true; + useTime = true; + if (hour == UnicodeString("numeric")) { + ADD_PATTERN("h"); + } else if (hour == UnicodeString("2-digit")) { + ADD_PATTERN("hh"); } - // Minute - UnicodeString minute = getFunctionOption(toFormat, opts, UnicodeString("minute"), errorCode); - if (U_FAILURE(errorCode)) { - errorCode = U_ZERO_ERROR; - } else { - useTime = true; - if (minute == UnicodeString("numeric")) { - ADD_PATTERN("m"); - } else if (minute == UnicodeString("2-digit")) { - ADD_PATTERN("mm"); - } + } + // Minute + UnicodeString minute = getFunctionOption(toFormat, opts, UnicodeString("minute"), errorCode); + if (U_FAILURE(errorCode)) { + errorCode = U_ZERO_ERROR; + } else { + fieldOptions = true; + useTime = true; + if (minute == UnicodeString("numeric")) { + ADD_PATTERN("m"); + } else if (minute == UnicodeString("2-digit")) { + ADD_PATTERN("mm"); } - // Second - UnicodeString second = getFunctionOption(toFormat, opts, UnicodeString("second"), errorCode); - if (U_FAILURE(errorCode)) { - errorCode = U_ZERO_ERROR; - } else { - useTime = true; - if (second == UnicodeString("numeric")) { - ADD_PATTERN("s"); - } else if (second == UnicodeString("2-digit")) { - ADD_PATTERN("ss"); - } + } + // Second + UnicodeString second = getFunctionOption(toFormat, opts, UnicodeString("second"), errorCode); + if (U_FAILURE(errorCode)) { + errorCode = U_ZERO_ERROR; + } else { + fieldOptions = true; + useTime = true; + if (second == UnicodeString("numeric")) { + ADD_PATTERN("s"); + } else if (second == UnicodeString("2-digit")) { + ADD_PATTERN("ss"); } } - /* - TODO - fractionalSecondDigits - hourCycle - timeZoneName - era - */ + } + /* + TODO + fractionalSecondDigits + hourCycle + timeZoneName + era + */ + if ((fieldOptions && (hasDateStyleOption || hasTimeStyleOption)) + && type == DateTimeFactory::DateTimeType::DateTime) { + // Can't have both field and style options + errorCode = U_MF_BAD_OPTION_ERROR; + return {}; + } + if (!useStyle) { df.adoptInstead(DateFormat::createInstanceForSkeleton(skeleton, errorCode)); } diff --git a/icu4c/source/i18n/messageformat2_function_registry_internal.h b/icu4c/source/i18n/messageformat2_function_registry_internal.h index 733fc5e945d5..6a6550cac9ec 100644 --- a/icu4c/source/i18n/messageformat2_function_registry_internal.h +++ b/icu4c/source/i18n/messageformat2_function_registry_internal.h @@ -122,11 +122,12 @@ namespace message2 { static Number integer(const Locale& loc); // These options have their own accessor methods, since they have different default values. - int32_t maximumFractionDigits(const FunctionOptions& options) const; - int32_t minimumFractionDigits(const FunctionOptions& options) const; - int32_t minimumSignificantDigits(const FunctionOptions& options) const; - int32_t maximumSignificantDigits(const FunctionOptions& options) const; - int32_t minimumIntegerDigits(const FunctionOptions& options) const; + int32_t digitSizeOption(const FunctionOptions&, const UnicodeString&, int32_t, UErrorCode&) const; + int32_t maximumFractionDigits(const FunctionOptions& options, UErrorCode& status) const; + int32_t minimumFractionDigits(const FunctionOptions& options, UErrorCode& status) const; + int32_t minimumSignificantDigits(const FunctionOptions& options, UErrorCode& status) const; + int32_t maximumSignificantDigits(const FunctionOptions& options, UErrorCode& status) const; + int32_t minimumIntegerDigits(const FunctionOptions& options, UErrorCode& status) const; bool usePercent(const FunctionOptions& options) const; const Locale& locale; diff --git a/icu4c/source/test/intltest/messageformat2test_read_json.cpp b/icu4c/source/test/intltest/messageformat2test_read_json.cpp index ddf93da632ce..a33a30276e7f 100644 --- a/icu4c/source/test/intltest/messageformat2test_read_json.cpp +++ b/icu4c/source/test/intltest/messageformat2test_read_json.cpp @@ -38,7 +38,7 @@ static UErrorCode getExpectedRuntimeErrorFromString(const std::string& errorName return U_MF_OPERAND_MISMATCH_ERROR; } if (errorName == "bad-option") { - return U_MF_FORMATTING_ERROR; + return U_MF_BAD_OPTION_ERROR; } if (errorName == "unknown-function") { return U_MF_UNKNOWN_FUNCTION_ERROR; @@ -194,10 +194,6 @@ static void runValidTest(TestMessageFormat2& icuTest, if (errorType.length() <= 0) { errorType = errors[0]["name"]; } - // See TODO(options); ignore these tests for now - if (errorType == "bad-option") { - return; - } test.setExpectedError(getExpectedRuntimeErrorFromString(errorType)); expectedError = true; } else if (defaultError.length() > 0) { @@ -337,10 +333,6 @@ void TestMessageFormat2::jsonTestsFromFiles(IcuTestErrorCode& errorCode) { // (This applies to the expected output for all the U_DUPLICATE_DECLARATION_ERROR tests) runTestsFromJsonFile(*this, "duplicate-declarations.json", errorCode); - // TODO(options): - // Bad options. The spec is unclear about this - // -- see https://github.com/unicode-org/message-format-wg/issues/738 - // The current behavior is to set a U_MF_FORMATTING_ERROR for any invalid options. runTestsFromJsonFile(*this, "invalid-options.json", errorCode); runTestsFromJsonFile(*this, "syntax-errors-end-of-input.json", errorCode); diff --git a/testdata/message2/invalid-options.json b/testdata/message2/invalid-options.json index 698583cd5578..ef78da9183fc 100644 --- a/testdata/message2/invalid-options.json +++ b/testdata/message2/invalid-options.json @@ -1,6 +1,6 @@ { "scenario": "Bad options for built-in functions", - "description": "Tests for the bad-option error; only run in ICU4C for now", + "description": "Tests for the bad-option error. These are not spec tests because what constitutes a valid integer value is implementation-dependent", "defaultTestProperties": { "locale": "en-US", "expErrors": [ @@ -10,38 +10,29 @@ ] }, "tests": [ - { "comment": "Neither impl validates options right now; see https://github.com/unicode-org/message-format-wg/issues/738", - "src": ".local $foo = {1 :number minimumIntegerDigits=-1} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + { "src": ".local $foo = {1 :number minimumIntegerDigits=-1} {{bar {$foo}}}", + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" }, { "src": ".local $foo = {1 :number minimumIntegerDigits=foo} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" }, { "src": ".local $foo = {1 :number minimumFractionDigits=foo} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" }, { "src": ".local $foo = {1 :number maximumFractionDigits=foo} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" }, { "src": ".local $foo = {1 :number minimumSignificantDigits=foo} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" }, { "src": ".local $foo = {1 :number maximumSignificantDigits=foo} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" }, { "src": ".local $foo = {1 :integer minimumIntegerDigits=foo} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" }, { "src": ".local $foo = {1 :integer maximumSignificantDigits=foo} {{bar {$foo}}}", - "ignoreCpp": "ICU4C doesn't validate options", - "ignoreJava": "ICU4J doesn't validate options" + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" } ] } diff --git a/testdata/message2/more-functions.json b/testdata/message2/more-functions.json index b34803635ce9..dd75c36b1ca7 100644 --- a/testdata/message2/more-functions.json +++ b/testdata/message2/more-functions.json @@ -112,6 +112,22 @@ "exp": "Default number: 1.234.567.890.123.456.789,987654!", "locale": "ro", "params": [{ "name": "val", "value": {"decimal": "1234567890123456789.987654321"} }] + }, + { + "comment": "Datetime can have style options or field options, but not both", + "src": "{$date :datetime dateStyle=full weekday=short}", + "exp": "{$date}", + "expErrors": [{"type": "bad-option"}], + "params": [{ "name": "date", "value": { "date": 1669261357000 }}], + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" + }, + { + "comment": "Datetime can have style options or field options, but not both", + "src": "{$date :datetime timeStyle=full day=numeric}", + "exp": "{$date}", + "expErrors": [{"type": "bad-option"}], + "params": [{ "name": "date", "value": { "date": 1669261357000 }}], + "ignoreJava": "ICU-22747 ICU4J doesn't validate options" } ] }