@@ -342,6 +342,11 @@ FunctionContext MessageFormatter::makeFunctionContext(const FunctionOptions& opt
342342 context.getErrors ().setFormattingError (functionName, status);
343343 return env.createFallback (fallbackStr, status);
344344 }
345+ if (status == U_MF_BAD_OPTION) {
346+ status = U_ZERO_ERROR;
347+ context.getErrors ().setBadOption (functionName, status);
348+ return env.createFallback (fallbackStr, status);
349+ }
345350 if (U_FAILURE (status)) {
346351 return env.bogus ();
347352 }
@@ -440,7 +445,16 @@ void MessageFormatter::formatPattern(MessageContext& context,
440445 const FunctionValue* val = partVal.getValue (status);
441446 // Shouldn't be null or a fallback
442447 U_ASSERT (U_SUCCESS (status));
448+
449+ // See comment in matchSelectorKeys()
450+ bool badSelectOption = !checkSelectOption (*val);
443451 result += val->formatToString (status);
452+
453+ if (badSelectOption) {
454+ context.getErrors ().setRecoverableBadOption (val->getFunctionName (), status);
455+ CHECK_ERROR (status);
456+ }
457+
444458 // Handle formatting errors. `formatToString()` can't take a context and thus can't
445459 // register an error directly
446460 if (status == U_MF_FORMATTING_ERROR) {
@@ -490,6 +504,30 @@ void MessageFormatter::resolveSelectors(MessageContext& context, Environment& en
490504 }
491505}
492506
507+ bool MessageFormatter::checkSelectOption (const FunctionValue& val) const {
508+ const UnicodeString& name = val.getFunctionName ();
509+
510+ if (name != UnicodeString (" number" ) && name != UnicodeString (" integer" )) {
511+ return true ;
512+ }
513+
514+ // Per the spec, if the "select" option is present, it must have been
515+ // set from a literal
516+
517+ // Returns false if the `select` option is present and it was not set from a literal
518+
519+ const FunctionOptions& opts = val.getResolvedOptions ();
520+
521+ // OK if the option wasn't present
522+ UErrorCode localErrorCode = U_ZERO_ERROR;
523+ opts.getFunctionOption (options::SELECT, localErrorCode);
524+ if (U_FAILURE (localErrorCode)) {
525+ return true ;
526+ }
527+ // Otherwise, return true if the option was set from a literal
528+ return opts.wasSetFromLiteral (options::SELECT);
529+ }
530+
493531// See https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#resolve-preferences
494532// `keys` and `matches` are vectors of strings
495533void MessageFormatter::matchSelectorKeys (const UVector& keys,
@@ -531,10 +569,27 @@ void MessageFormatter::matchSelectorKeys(const UVector& keys,
531569 // Call the selector
532570 // Caller checked for fallback, so it's safe to call getValue()
533571 const FunctionValue* rvVal = rv.getValue (status);
572+
573+ // This condition can't be checked in the selector.
574+ // Effectively, there are two different kinds of "bad option" errors:
575+ // one that can be recovered from (used for select=$var) and one that
576+ // can't (used for bad digit size options and other cases).
577+ // The checking of the recoverable error has to be done here; otherwise,
578+ // the "bad option" signaled by the selector implementation would cause
579+ // fallback output to be used when formatting the `*` pattern.
580+ bool badSelectOption = !checkSelectOption (*rvVal);
581+
534582 U_ASSERT (U_SUCCESS (status));
535583 rvVal->selectKeys (adoptedKeys.getAlias (), keysLen, prefsArr, prefsLen,
536584 status);
537585
586+ if (badSelectOption) {
587+ context.getErrors ().setRecoverableBadOption (rvVal->getFunctionName (), status);
588+ CHECK_ERROR (status);
589+ // In this case, only the `*` variant should match
590+ prefsLen = 0 ;
591+ }
592+
538593 // Update errors
539594 if (savedStatus != status) {
540595 if (U_FAILURE (status)) {
0 commit comments