Skip to content
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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

Fix fallback value definition and use #903

wants to merge 13 commits into from

Conversation

eemeli
Copy link
Collaborator

@eemeli eemeli commented Oct 10, 2024

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.

@eemeli eemeli added formatting LDML46.1 MF2.0 Draft Candidate labels Oct 10, 2024
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
Co-authored-by: Addison Phillips <[email protected]>
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
Comment on lines 386 to 400
- _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`.
Copy link
Collaborator

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}.

Copy link
Collaborator Author

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:

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.

Copy link
Collaborator

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.

Copy link
Member

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)

spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
spec/formatting.md Outdated Show resolved Hide resolved
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.
Copy link
Collaborator

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?

Copy link
Collaborator

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]>
aphillips added a commit that referenced this pull request Oct 28, 2024
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.
aphillips added a commit that referenced this pull request Oct 28, 2024
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.
@eemeli
Copy link
Collaborator Author

eemeli commented Nov 12, 2024

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_.
Copy link
Member

@macchiati macchiati Nov 12, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
formatting LDML46.1 MF2.0 Draft Candidate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants