@@ -69,7 +69,7 @@ static UnicodeString functionFallback(const InternalValue& operand,
6969 fallbackToUse += var;
7070 }
7171 // If it exists, create a BaseValue (FunctionValue) for it
72- LocalPointer<BaseValue> result (BaseValue::create (locale, fallbackToUse, *val, errorCode));
72+ LocalPointer<BaseValue> result (BaseValue::create (locale, fallbackToUse, *val, false , errorCode));
7373 // Add fallback and return an InternalValue
7474 if (U_SUCCESS (errorCode)) {
7575 return InternalValue (result.orphan (), fallbackToUse);
@@ -112,6 +112,7 @@ static UnicodeString reserialize(const UnicodeString& s) {
112112 LocalPointer<BaseValue> val (BaseValue::create (locale,
113113 fallbackToUse,
114114 Formattable (lit.unquoted ()),
115+ true ,
115116 errorCode));
116117 if (U_SUCCESS (errorCode)) {
117118 return InternalValue (val.orphan (), fallbackToUse);
@@ -156,9 +157,24 @@ static UnicodeString reserialize(const UnicodeString& s) {
156157 rhs.update (result.asFallback ());
157158 } else {
158159 U_ASSERT (result.isEvaluated ());
160+
161+ // Need to create a new InternalValue such that the inner
162+ // FunctionValue does _not_ return true for wasSetFromLiteral()
163+ const FunctionValue* inner = result.getValue (status);
164+ U_ASSERT (U_SUCCESS (status)); // Already checked that result is evaluated
165+ LocalPointer<FunctionValue> variableValue (static_cast <FunctionValue*>(VariableValue::create (inner, status)));
166+ if (U_FAILURE (status) || !variableValue.isValid ()) {
167+ return result;
168+ }
169+
170+ InternalValue wrappedResult (variableValue.orphan (), result.asFallback ());
171+ InternalValue& ref = env.createUnnamed (std::move (wrappedResult), status);
172+ if (U_FAILURE (status)) {
173+ return result;
174+ }
159175 // Create an indirection to the result returned
160176 // by evalExpression()
161- rhs.update (result );
177+ rhs.update (ref );
162178 }
163179 return rhs;
164180 }
@@ -241,7 +257,7 @@ FunctionOptions MessageFormatter::resolveOptions(Environment& env,
241257 }
242258
243259 // The option is resolved; add it to the vector
244- ResolvedFunctionOption resolvedOpt (k, *optVal);
260+ ResolvedFunctionOption resolvedOpt (k, *optVal, false );
245261 LocalPointer<ResolvedFunctionOption>
246262 p (create<ResolvedFunctionOption>(std::move (resolvedOpt), status));
247263 EMPTY_ON_ERROR (status);
@@ -420,6 +436,81 @@ FunctionContext MessageFormatter::makeFunctionContext(const FunctionOptions& opt
420436 }
421437}
422438
439+ // Evaluates `rand` and requires the value to be a string, setting `result` to it
440+ // if so, and setting a bad option error if not
441+ bool MessageFormatter::operandToStringWithBadOptionError (MessageContext& context,
442+ Environment& globalEnv,
443+ const Operand& rand,
444+ UnicodeString& result,
445+ UErrorCode& status) const {
446+ EMPTY_ON_ERROR (status);
447+
448+ InternalValue& iVal = evalOperand ({}, globalEnv, rand, context, status);
449+ EMPTY_ON_ERROR (status);
450+ const FunctionValue* val = iVal.getValue (status);
451+ U_ASSERT (U_SUCCESS (status));
452+
453+ result = val->getOperand ().getString (status);
454+ if (U_FAILURE (status)) {
455+ status = U_ZERO_ERROR;
456+ context.getErrors ().setBadOption ({}, status);
457+ return false ;
458+ }
459+ return true ;
460+ }
461+
462+ bool isValidLocale (const UnicodeString& localeID) {
463+ std::string asString;
464+ Locale loc (localeID.toUTF8String (asString).c_str ());
465+ int32_t count = 0 ;
466+ const Locale* availableLocales = Locale::getAvailableLocales (count);
467+
468+ for (int32_t i = 0 ; i < count; i++) {
469+ if (availableLocales[i] == loc) {
470+ return true ;
471+ }
472+ }
473+ return false ;
474+ }
475+
476+ // Validates u: options on markup parts -- see
477+ // https://github.com/unicode-org/message-format-wg/blob/main/spec/u-namespace.md
478+ void MessageFormatter::validateUOptionsOnMarkup (MessageContext& context,
479+ Environment& globalEnv,
480+ const Markup& markupPart,
481+ UErrorCode& status) const {
482+ CHECK_ERROR (status);
483+
484+ const OptionMap& opts = markupPart.getOptionsInternal ();
485+ for (int32_t i = 0 ; i < opts.len ; i++) {
486+ const Option& opt = opts.options [i];
487+ const UnicodeString& optionName = opt.getName ();
488+ const Operand& optionValue = opt.getValue ();
489+
490+ if (optionName == options::U_ID) {
491+ UnicodeString ignore;
492+ operandToStringWithBadOptionError (context, globalEnv, optionValue, ignore, status);
493+ } else if (optionName == options::U_LOCALE) {
494+ UnicodeString asString;
495+ if (operandToStringWithBadOptionError (context, globalEnv, optionValue, asString, status)) {
496+ if (!isValidLocale (asString)) {
497+ context.getErrors ().setBadOption ({}, status);
498+ }
499+ }
500+ } else if (optionName == options::U_DIR) {
501+ UnicodeString asString;
502+ if (operandToStringWithBadOptionError (context, globalEnv, optionValue, asString, status)) {
503+ if (!(asString == options::LTR || asString == options::RTL
504+ || asString == options::AUTO || asString == options::INHERIT)) {
505+ context.getErrors ().setBadOption ({}, status);
506+ }
507+ }
508+ }
509+ // Any other options are ignored
510+ }
511+ }
512+
513+
423514// Formats each text and expression part of a pattern, appending the results to `result`
424515void MessageFormatter::formatPattern (MessageContext& context,
425516 Environment& globalEnv,
@@ -432,7 +523,7 @@ void MessageFormatter::formatPattern(MessageContext& context,
432523 if (part.isText ()) {
433524 result += part.asText ();
434525 } else if (part.isMarkup ()) {
435- // Markup is ignored
526+ validateUOptionsOnMarkup (context, globalEnv, part. asMarkup (), status);
436527 } else {
437528 // Format the expression
438529 InternalValue& partVal = evalExpression ({}, globalEnv, part.contents (), context, status);
0 commit comments