diff --git a/spec/formatting.md b/spec/formatting.md
index b8f6d22ae..4ac0ca295 100644
--- a/spec/formatting.md
+++ b/spec/formatting.md
@@ -231,8 +231,7 @@ whether its value was originally a _quoted literal_ or an _unquoted literal_.
> For example,
> the _option_ `foo=42` and the _option_ `foo=|42|` are treated as identical.
-
-> For example, in a JavaScript formatter
+> For example, in a JavaScript formatter,
> the _resolved value_ of a _text_ or a _literal_ could have the following implementation:
>
> ```ts
@@ -257,9 +256,14 @@ Otherwise, the _variable_ is an implicit reference to an input value,
and its value is looked up from the _formatting context_ _input mapping_.
The resolution of a _variable_ fails if no value is identified for its _name_.
-If this happens, an _Unresolved Variable_ error is emitted.
-If a _variable_ would resolve to a _fallback value_,
-this MUST also be considered a failure.
+If this happens, an _Unresolved Variable_ error is emitted
+and a _fallback value_ is used as the _resolved value_ of the _variable_.
+
+If the _resolved value_ identified for the _variable_ _name_ is a _fallback value_,
+a _fallback value_ is used as the _resolved value_ of the _variable_.
+
+The _fallback value_ representation of a _variable_ has a string representation
+consisting of the U+0024 DOLLAR SIGN `$` followed by the _name_ of the _variable_.
### Function Resolution
@@ -267,13 +271,15 @@ To resolve an _expression_ with a _function_,
the following steps are taken:
1. If the _expression_ includes an _operand_, resolve its value.
- If this fails, use a _fallback value_ for the _expression_.
-2. Resolve the _identifier_ of the _function_ and, based on the starting sigil,
+ If this is a _fallback value_,
+ return a _fallback value_ as the _resolved value_ of the _expression_.
+
+2. Resolve the _identifier_ of the _function_ and
find the appropriate _function handler_ to call.
If the implementation cannot find the _function handler_,
or if the _identifier_ includes a _namespace_ that the implementation does not support,
emit an _Unknown Function_ error
- and use a _fallback value_ for the _expression_.
+ and return a _fallback value_ as the _resolved value_ of the _expression_.
Implementations are not required to implement _namespaces_ or installable
_function registries_.
@@ -294,7 +300,7 @@ the following steps are taken:
supported by the implementation, process them as specified.
Such `u:` options MAY be removed from the resolved mapping of _options_.
-5. Call the function implementation with the following arguments:
+5. Call the _function handler_ with the following arguments:
- The _function context_.
- The resolved mapping of _options_.
@@ -318,7 +324,7 @@ the following steps are taken:
_operand_ did not match that expected by the _function_,
the _function_ SHOULD cause a _Bad Operand_ error to be emitted.
- In all failure cases, use the _fallback value_ for the _expression_ as its _resolved value_.
+ In all failure cases, return a _fallback value_ as the _resolved value_ of the _expression_.
#### Function Handler
@@ -366,17 +372,27 @@ The order of _options_ MUST NOT be significant.
For each _option_:
-- Resolve the _identifier_ of the _option_.
-- If the _option_'s right-hand side successfully resolves to a value,
- bind the _identifier_ of the _option_ to the _resolved value_ in the mapping.
-- Otherwise, bind the _identifier_ of the _option_ to an unresolved value in the mapping.
- Implementations MAY later remove this value before calling the _function_.
- (Note that an _Unresolved Variable_ error will have been emitted.)
+1. Let `res` be a new empty mapping.
+1. For each _option_:
+ 1. Let `id` be the string value of the _identifier_ of the _option_.
+ 1. Let `rv` be the _resolved value_ of the _option_ value.
+ 1. If `rv` is a _fallback value_:
+ 1. If supported, emit a _Bad Option_ error.
+ 1. Else:
+ 1. Set `res[id]` to be `rv`.
+1. Return `res`.
-Errors MAY be emitted during _option resolution_,
-but it always resolves to some mapping of string identifiers to values.
+The result of _option resolution_ MUST be a (possibly empty) mapping
+of string identifiers to values;
+that is, errors MAY be emitted, but such errors MUST NOT be fatal.
This mapping can be empty.
+> [!NOTE]
+> The _resolved value_ of a _function_ _operand_
+> can also include resolved option values.
+> These are not included in the _option resolution_ result,
+> and need to be processed separately by a _function handler_.
+
### Markup Resolution
Unlike _functions_, the resolution of _markup_ is not customizable.
@@ -395,17 +411,28 @@ The resolution of _markup_ MUST always succeed.
### Fallback Resolution
-A **_fallback value_** is the _resolved value_ for an _expression_ that fails to resolve.
+A **_fallback value_** is the _resolved value_ for
+an _expression_ or _variable_ when that _expression_ or _variable_ fails to resolve.
+It contains a string representation that is used for its formatting,
+and no option values.
+
+The _resolved value_ of _text_, _literal_, and _markup_ MUST NOT be a _fallback value_.
+
+A _variable_ fails to resolve when no value is identified for its _name_.
+The string representation of its _fallback value_ is
+U+0024 DOLLAR SIGN `$` followed by the _name_ of the _variable_.
An _expression_ fails to resolve when:
-- A _variable_ used as an _operand_ (with or without a _function_) fails to resolve.
- * Note that this does not include a _variable_ used as an _option_ value.
-- A _function_ fails to resolve.
+- A _variable_ used as its _operand_ resolves to a _fallback value_.
+ Note that an _expression_ does not necessarily fail to resolve
+ if an _option_ resolves with a _fallback value_.
+- No _function handler_ is found for a _function_ _identifier_.
+- Calling a _function handler_ fails or does not return a valid value.
-The _fallback value_ depends on the contents of the _expression_:
+The string representation of the _fallback value_ of an _expression_ depends on its contents:
-- _expression_ with a _literal_ _operand_ (either quoted or unquoted)
+- _expression_ with a _literal_ _operand_ (either quoted or unquoted):
U+007C VERTICAL LINE `|`
followed by the value of the _literal_
with escaping applied to U+005C REVERSE SOLIDUS `\` and U+007C VERTICAL LINE `|`,
@@ -413,49 +440,44 @@ The _fallback value_ depends on the contents of the _expression_:
> Examples:
> In a context where `:func` fails to resolve,
- > `{42 :func}` resolves to the _fallback value_ `|42|` and
- > `{|C:\\| :func}` resolves to the _fallback value_ `|C:\\|`.
+ > `{42 :func}` resolves to a _fallback value_ with a string representation `|42|` and
+ > `{|C:\\| :func}` resolves to a _fallback value_ with a string representation `|C:\\|`.
-- _expression_ with _variable_ _operand_ referring to a local _declaration_ (with or without a _function_):
- the _value_ to which it resolves (which may already be a _fallback value_)
-
- > Examples:
- > In a context where `:func` fails to resolve,
- > the _pattern_'s _expression_ in `.local $var={|val|} {{{$var :func}}}`
- > resolves to the _fallback value_ `|val|` and the message formats to `{|val|}`.
- > In a context where `:now` fails to resolve but `:datetime` does not,
- > the _pattern_'s _expression_ in
- > ```
- > .local $t = {:now format=iso8601}
- > .local $pretty_t = {$t :datetime}
- > {{{$pretty_t}}}
- > ```
- > (transitively) resolves to the _fallback value_ `:now` and
- > the message formats to `{:now}`.
-
-- _expression_ with _variable_ _operand_ not referring to a local _declaration_ (with or without a _function_):
+- _expression_ with _variable_ _operand_:
+ the _fallback value_ representation of that _variable_,
U+0024 DOLLAR SIGN `$` followed by the _name_ of the _variable_
> Examples:
> In a context where `$var` fails to resolve, `{$var}` and `{$var :number}`
- > both resolve to the _fallback value_ `$var`.
+ > both resolve to a _fallback value_ with a string representation `$var`
+ > (even if `:number` fails to resolve).
+ >
> In a context where `:func` fails to resolve,
- > the _pattern_'s _expression_ in `.input $arg {{{$arg :func}}}`
- > resolves to the _fallback value_ `$arg` and
- > the message formats to `{$arg}`.
+ > the _placeholder_ in `.local $var = {|val| :func} {{{$var}}}`
+ > resolves to a _fallback value_ with a string representation `$var`.
+ >
+ > In a context where either `:now` or `:pretty` fails to resolve,
+ > the _placeholder_ in
+ > ```
+ > .local $time = {:now format=iso8601}
+ > {{{$time :pretty}}}
+ > ```
+ > resolves to a _fallback value_ with a string representation `$time`.
- _function_ _expression_ with no _operand_:
U+003A COLON `:` followed by the _function_ _identifier_
> Examples:
- > In a context where `:func` fails to resolve, `{:func}` resolves to the _fallback value_ `:func`.
- > In a context where `:ns:func` fails to resolve, `{:ns:func}` resolves to the _fallback value_ `:ns:func`.
+ > In a context where `:func` fails to resolve,
+ > `{:func}` resolves to a _fallback value_ with a string representation `:func`.
+ > In a context where `:ns:func` fails to resolve,
+ > `{:ns:func}` resolves to a _fallback value_ with a string representation `:ns:func`.
- Otherwise: the U+FFFD REPLACEMENT CHARACTER `�`
This is not currently used by any expression, but may apply in future revisions.
-_Option_ _identifiers_ and values are not included in the _fallback value_.
+_Options_ and _attributes_ are not included in the _fallback value_.
_Pattern selection_ is not supported for _fallback values_.
diff --git a/test/tests/fallback.json b/test/tests/fallback.json
new file mode 100644
index 000000000..fd1429c9b
--- /dev/null
+++ b/test/tests/fallback.json
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://raw.githubusercontent.com/unicode-org/message-format-wg/main/test/schemas/v0/tests.schema.json",
+ "scenario": "Fallback",
+ "description": "Test cases for fallback behaviour.",
+ "defaultTestProperties": {
+ "bidiIsolation": "none",
+ "locale": "en-US",
+ "expErrors": true
+ },
+ "tests": [
+ {
+ "description": "function with unquoted literal operand",
+ "src": "{42 :test:function fails=format}",
+ "exp": "{|42|}"
+ },
+ {
+ "description": "function with quoted literal operand",
+ "src": "{|C:\\\\| :test:function fails=format}",
+ "exp": "{|C:\\\\|}"
+ },
+ {
+ "description": "unannotated implicit input variable",
+ "src": "{$var}",
+ "exp": "{$var}"
+ },
+ {
+ "description": "annotated implicit input variable",
+ "src": "{$var :number}",
+ "exp": "{$var}"
+ },
+ {
+ "description": "local variable with unknown function in declaration",
+ "src": ".local $var = {|val| :test:undefined} {{{$var}}}",
+ "exp": "{$var}"
+ },
+ {
+ "description": "function with local variable operand with unknown function in declaration",
+ "src": ".local $var = {|val| :test:undefined} {{{$var :test:function}}}",
+ "exp": "{$var}"
+ },
+ {
+ "description": "local variable with unknown function in placeholder",
+ "src": ".local $var = {|val|} {{{$var :test:undefined}}}",
+ "exp": "{$var}"
+ },
+ {
+ "description": "function with no operand",
+ "src": "{:test:undefined}",
+ "exp": "{:test:undefined}"
+ }
+ ]
+}
diff --git a/test/tests/functions/number.json b/test/tests/functions/number.json
index d3d76fb7f..ed95a8a10 100644
--- a/test/tests/functions/number.json
+++ b/test/tests/functions/number.json
@@ -132,33 +132,15 @@
},
{
"src": ".local $foo = {$bar :number minimumFractionDigits=foo} {{bar {$foo}}}",
- "params": [
- {
- "name": "bar",
- "value": 4.2
- }
- ],
- "exp": "bar {$bar}",
- "expErrors": [
- {
- "type": "bad-option"
- }
- ]
+ "params": [{ "name": "bar", "value": 4.2 }],
+ "exp": "bar {$foo}",
+ "expErrors": [{ "type": "bad-option" }]
},
{
"src": ".local $foo = {$bar :number} {{bar {$foo}}}",
- "params": [
- {
- "name": "bar",
- "value": "foo"
- }
- ],
- "exp": "bar {$bar}",
- "expErrors": [
- {
- "type": "bad-operand"
- }
- ]
+ "params": [{ "name": "bar", "value": "foo" }],
+ "exp": "bar {$foo}",
+ "expErrors": [{ "type": "bad-operand" }]
},
{
"src": ".input {$foo :number} {{bar {$foo}}}",