-
-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix fallback value definition and use #903
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Addison Phillips <[email protected]>
Co-authored-by: Tim Chevalier <[email protected]>
spec/formatting.md
Outdated
- _expression_ with a _variable_ _operand_ referring to a _declaration_: | ||
the string representation of the _fallback value_ of the _expression_ of that _declaration_. | ||
|
||
> 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 | ||
> the _placeholder_ in `.local $var = {|val| :func} {{{$var}}}` | ||
> resolves to the _fallback value_ `|val|`. | ||
> | ||
> In a context where `:pretty` fails to resolve but `:now` does not, | ||
> the _placeholder_ in | ||
> ``` | ||
> .local $t = {:now format=iso8601} | ||
> .local $pretty_t = {$t :datetime} | ||
> {{{$pretty_t}}} | ||
> {{{$t :pretty}}} | ||
> ``` | ||
> (transitively) resolves to the _fallback value_ `:now` and | ||
> the message formats to `{:now}`. | ||
> resolves to the _fallback value_ `:now`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a behaviorally distinct change that I hope is not intentional... the current example is attempting to demonstrate transitive fallback such that an a variable defined as output from a defined function that uses as its operand a different variable defined as output from a base function that fails to resolve is the fallback value from that base resolution failure:
.local $t = {:now format=iso8601}
.local $pretty_t = {$t :datetime}
{{{$pretty_t}}}
formatting as {:now}
to indicate the broken function reference through the non-broken function expression, whereas the new scenario
.local $t = {:now format=iso8601}
{{{$t :pretty}}}
in which :now
resolves but :pretty
does not fail to capture those details, and AFAIK should format as the non-fallback output from {:now format=iso8601}
.
At the root of it, I don't see why the fallback value of an expression with a declaration-referencing operand should be the fallback value of that declaration rather than its resolved value. So looking again at
.local $t = {:now format=iso8601}
{{{$t :pretty}}}
only if :now
failed to resolve would I expect the result to format as {:now}
, because otherwise we have a better value for downstream fallbacks—the resolved value of {:now format=iso8601}
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When reviewing #539, I had not noticed that this behaviour changed. Before that PR, we had this included in the spec:
message-format-wg/spec/formatting.md
Lines 284 to 288 in 770cb66
When an error occurs in an _expression_ with a _variable_ _operand_ | |
and the _variable_ refers to a local _declaration_, | |
the _fallback value_ is formatted based on the _expression_ | |
on the right-hand side of the _declaration_, | |
rather than the _expression_ in the _selector_ or _pattern_. |
(note that this was from before introducing input/local, so "local declaration" here meant all declarations)
The language proposed here would indeed revert to the old behaviour, where a failure in a later function would end up using the fallback value of the earliest declaration that its operand is built from, as opposed to the resolved value of its most recent declaration.
The rationalisation for this behaviour is presented in #539 (comment): when any failure happens in the resolution of an expression, we should not trust that the resolved value of its operand is by default serialisable. For example, in:
.local $user = {$user_id :get-user}
{{Add {$user :first-nam} as friend}}
If :get-user
succeeds but :first-nam
is a typo that's not caught before we try to format the message, there is a risk that the default serialisation of the $user
value would reveal information that is not intended to be public. Therefore, the safe formatting result here must be Add {$user_id} as friend
.
If reverting to this behaviour is not acceptable, then we ought to add a requirement on all resolved values to provide some safe serialization, in case their values are unintentionally included in the formatted result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a very reasonable concern that I find compelling. And (assuming agreement from others) the spec should be very clear with both text and examples about the consequences—that resolution failures propagate through operands, locking fallback values to the earliest such point in the causal chain regardless of success/failure of downstream functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get it? I mean, the default serialization of $user
might reveal inner data of the user object. but the fallback value doesn't serialize that object. It just reveals the name of that object is $user
. The only time we expose a serialization is when the operand is a literal, in which case the value is already exposed in the message. So there is no danger of $user
's content being exposed, only its name.
I guess I'm also wondering why we walk back up the declaration chain to begin with. The example in the spec is:
.local $t = {:now format=iso8601}
.local $pretty_t = {$t :datetime}
{{{$pretty_t}}}
Shouldn't the fallback value be "at the point of failure"? What's wrong with {$pretty_t}
coming out? Or {$t}
if :datetime
failed? Emiting :now
doesn't tell you where the failure is and might point at the wrong element in the chain (imagine that :datetime
were misspelled in the example)
Co-authored-by: Richard Gibson <[email protected]>
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first mention I've noticed in the spec of a non-fatal error, i.e. a warning. If Bad Option is sometimes a fatal error and sometimes a warning, it seems like the spec needs some more language to clarify what implementations should do with it. Or if it's meant to always be a warning, maybe https://github.com/unicode-org/message-format-wg/blob/main/spec/errors.md should say something about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought: from the perspective of an implementation can only signal errors or return usable output, but not both, I'm reading this as saying that the implementation can't signal a Bad Option error. Which is fine, since it's a "MAY".
Co-authored-by: Tim Chevalier <[email protected]>
This addresses concerns raised in #903 - define `option resolution` as a term - require that option order is not significant I was tempted to define the term `resolved options`, but held back.
This addresses concerns raised in #903 - define `option resolution` as a term - require that option order is not significant I was tempted to define the term `resolved options`, but held back.
Updated, as per today's discussions. The fallback variable name is now the last one, and it may be a local variable name. |
@@ -259,6 +259,12 @@ The resolution of a _variable_ fails if no value is identified for its _name_. | |||
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_. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A general issue: The resolved value is logically {datatype, optionMap}. So whenever we have a statement like this, that implies that a fallback value is a resolved value. That means we must say what the optionMap is: I presume an empty map, but we need to be clear.
I think for this case, we can probably put this in one place, the definition of fallback value. We should check other instances of 'resolved value' for completeness.
This is a continuation for the changes of #728, defining a fallback value explicitly as a resolved value, and rephrasing the parts of the spec that refer to resolution failures to instead test for whether a resolved value is a fallback value.