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

Clarifications to dictionary semantics #859

Merged
merged 4 commits into from
Mar 25, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 55 additions & 53 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -2279,7 +2279,7 @@ corresponding argument omitted.
If the type of an argument is a [=dictionary type=] or a [=union type=] that has
a [=dictionary type=] as one
of its [=flattened member types=], and that dictionary type and its ancestors have no
[=required dictionary member|required members=], and the argument is either the final argument or is
[=dictionary member/required=] [=dictionary member|members=], and the argument is either the final argument or is
followed only by [=optional arguments=], then the argument must be specified as
optional and have a default value provided.

Expand Down Expand Up @@ -3877,8 +3877,8 @@ defined with [=optional arguments=] and merged into one,
};
</pre>

the [=overload resolution algorithm=] would treat the <var ignore>path</var> argument as [=not
present=] given the same call <code>stroke(undefined)</code>, and not throw any exceptions.
domenic marked this conversation as resolved.
Show resolved Hide resolved
the [=overload resolution algorithm=] would treat the <var ignore>path</var> argument as missing
given the same call <code>stroke(undefined)</code>, and not throw any exceptions.

Note: For this particular example, the latter behavior is actually what Web developers would
generally expect. If {{CanvasDrawPath}} were to be designed today, [=optional arguments=] would be
Expand Down Expand Up @@ -4062,8 +4062,7 @@ determine what Web IDL language feature to use:
class="idl">foo(|arg|)</code> operation, with |arg| set to null, while <code>foo()</code> alone
would go to the first overload. This can be a surprising behavior for many API users. Instead,
specification authors are encouraged to use an [=optional argument=], which would categorize
both <code>foo()</code> and <code>foo(undefined)</code> as "|arg| is [=not
present=]".
both <code>foo()</code> and <code>foo(undefined)</code> as "|arg| is missing".

<pre highlight="webidl">
interface A {
Expand Down Expand Up @@ -4741,9 +4740,15 @@ where [=map/keys=] are strings and [=map/values=] are of a particular type speci
};
</pre>

Dictionaries are always passed by value. In language bindings where a dictionary is represented by an object of some kind, passing a
dictionary to a [=platform object=] will not result in a reference to the dictionary being kept by that object.
Similarly, any dictionary returned from a platform object will be a copy and modifications made to it will not be visible to the platform object.
Dictionaries are always passed by value: the dictionary does not retain a reference to its
language-specific representation (e.g., the corresponding ECMAScript object). So for example,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the parts before and after the colon are very different things. To me, having IDL dictionaries be passed by value means (and I'm not saying this is accurate) that in a scenario like:

  1. Let dictionary be an IDL dictionary.
  2. Perform some algorithm X given dictionary.

where "some algorithm X" modifies the given dictionary, then any modifications to dictionary will not be observable directly outside of it.

But I feel like the actual point here is that the ES-to-IDL conversion process destroys all links with the original object, unlike the case with interface types. Maybe we need some different wording to get that across.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by "will not be observable directly outside of it", except for the idea that when the dictionary is converted to ES, it will not have such a link. So they seem like the same before and after the colon to me.

That said, I'd be happy to just remove "Dictionaries are always passed by value". "Pass by value" is a notoriously confusing concept and it might be better to just explicitly state what we mean, as the parts after the colon try to do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removal sounds good to me.

I understood "pass by value" as passing the IDL dictionaries passed around different spec-level algorithms by value, while you understood it as passing ES objects around into IDL operations by value. See if this helps with understanding.

returning a dictionary from an [=operation=] will result in a new ECMAScript object being created
domenic marked this conversation as resolved.
Show resolved Hide resolved
from the current values of the dictionary. And, an operation that accepts a dictionary as an
argument will perform a one-time conversion from the given ECMAScript value into the dictionary,
based on the current properties of the ECMAScript object. Modifications to the dictionary will not
be reflected in the corresponding ECMAScript object, and vice-versa.

Dictionaries must not be used as the type of an [=attribute=] or [=constant=].

A dictionary can be defined to <dfn id="dfn-inherit-dictionary" for="dictionary" export>inherit</dfn> from another dictionary.
If the identifier of the dictionary is followed by a colon and a [=identifier=],
Expand Down Expand Up @@ -4772,30 +4777,42 @@ from another dictionary, then the set is empty. Otherwise, the set
includes the dictionary |E| that |D| [=interface/inherits=]
from and all of |E|’s [=inherited dictionaries=].

A dictionary value of type |D| can have key–value pairs corresponding
to the dictionary members defined on |D| and on any of |D|’s
[=inherited dictionaries=].
On a given dictionary value, the presence of each dictionary member
is optional, unless that member is specified as required.
A dictionary member is said to be
<dfn id="dfn-present" export lt="present|not present" for="dictionary member">present</dfn>
in a dictionary value if the value [=map/exists|contains an entry with the key=]
given by the member's [=identifier=], otherwise it is [=not present=].
Dictionary members can also optionally have a <dfn id="dfn-dictionary-member-default-value" for="dictionary member" export>default value</dfn>, which is
the value to use for the dictionary member when passing a value to a
[=platform object=] that does
not have a specified value. Dictionary members with default values are
always considered to be present.
[=Dictionary members=] can be specified as
<dfn id="required-dictionary-member" export for="dictionary member">required</dfn>, meaning that
converting a language-specific value to a dictionary requires providing a value for that member.
They can also be specified as having a
<dfn id="dfn-dictionary-member-default-value" for="dictionary member" export>default value</dfn>,
which is the value used by default when author code or specification text does not provide a value
for that member. Note that [=dictionary member/required=] dictionary members only have meaning for
dictionaries used as operation arguments; members should be left optional if a dictionary is only
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, right? Why is this a should and are implementers on board with enforcing this so we can ensure it doesn't end up in specifications?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new. It's just a should saying not to add pointless stuff. It's not likely to be easily enforceable on an implementation level because it would require whole-corpus analysis to find all the dictionary usage sites.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not entirely clear to me if it's pointless as it's basically a specification-level assert. But since it can't be used consistently (unless you always have distinct output dictionaries, which also isn't great), I'm inclined to agree it's better not to add it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the "only have meaning" part isn't clear: of course the word "required" has meaning in general!

How about

Note that specifying dictionary members as required only has an observable effect when converting other representations of dictionaries (like an ECMAScript value supplied as an argument to an operation) to an IDL dictionary. Specification authors should leave members optional in all other cases, including when a dictionary type is used solely as the return type of operations.

We should also define "optional" as "not required" in the context of dictionary members.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section isn't talking about the word "required" in general. It's say "required dictionary members only have meaning...". So I think it's still fairly clear. Your rephrasing is OK too though.

used as a return value.

A given dictionary value of type |D| can have [=map/entries=] for each of the dictionary members
defined on |D| and on any of |D|’s [=inherited dictionaries=]. Dictionary members that are specified
as [=dictionary member/required=], or that are specified as having a
[=dictionary member/default value=], will always have such [=map/entries=]. Other members are
optional, and their entries might or might not [=map/exist=] in the dictionary value.

<p class="note">
In the ECMAScript binding, a value of <emu-val>undefined</emu-val> is treated as
[=not present=], or will trigger the [=dictionary member/default value=] where applicable.
In the ECMAScript binding, a value of <emu-val>undefined</emu-val> for the property
corresponding to a [=dictionary member=] is treated the same as omitting that property. Thus, it
will cause an error if the member is [=dictionary member/required=], or will trigger the
[=dictionary member/default value=] if one is present, or will result in no [=map/entry=]
existing in the dictionary value otherwise.
</p>

<p class="advisement">
As with [=optional argument/default value|operation argument default values=], it is strongly
suggested not to use <emu-val>true</emu-val> as the [=dictionary member/default value=] for
domenic marked this conversation as resolved.
Show resolved Hide resolved
{{boolean}}-typed [=dictionary members=], as this can be confusing for authors who might
otherwise expect the default conversion of <emu-val>undefined</emu-val> to be used (i.e.,
<emu-val>false</emu-val>).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do that in a followup if desired, for both dictionaries and boolean arguments in general.

</p>

An [=ordered map=] with string [=map/keys=] can be implicitly treated as a dictionary value of a
specific dictionary |D| if all of its [=map/entries=] correspond to [=dictionary members=], in the
correct order and with the correct types, and with appropriate [=map/entries=] for any required
dictionary members.
correct order and with the correct types, and with appropriate [=map/entries=] for any required or
domenic marked this conversation as resolved.
Show resolved Hide resolved
defaulted dictionary members.

<div class="example">
<pre highlight="webidl">
Expand All @@ -4811,16 +4828,6 @@ dictionary members.
1. Return «[ "name" → "test", "serviceIdentifiers" → |identifiers| ]».
</div>

<p class="advisement">
As with [=optional argument/default value|operation argument default values=],
it is strongly suggested not to use <emu-val>true</emu-val> as the
[=dictionary member/default value=] for
{{boolean}}-typed
[=dictionary members=],
as this can be confusing for authors who might otherwise expect the default
conversion of <emu-val>undefined</emu-val> to be used (i.e., <emu-val>false</emu-val>).
</p>

Each [=dictionary member=] (matching
<emu-nt><a href="#prod-DictionaryMember">DictionaryMember</a></emu-nt>) is specified
as a type (matching <emu-nt><a href="#prod-Type">Type</a></emu-nt>) followed by an
Expand Down Expand Up @@ -4876,9 +4883,8 @@ is an [=enumeration=], then its
be one of the [=enumeration values|enumeration’s values=].

If the type of the dictionary member is preceded by the
<emu-t>required</emu-t> keyword, the member is considered a
<dfn id="required-dictionary-member" export>required dictionary member</dfn>
and must be present on the dictionary.
<emu-t>required</emu-t> keyword, the member is considered a [=dictionary member/required=]
[=dictionary member=].

<pre highlight="webidl" class="syntax">
dictionary identifier {
Expand Down Expand Up @@ -5000,10 +5006,6 @@ The identifier of a dictionary member must not be
the same as that of another dictionary member defined on the dictionary or
on that dictionary’s [=inherited dictionaries=].

Dictionaries must not be used as the type of an
[=attribute=] or
[=constant=].

No [=extended attributes=] are applicable to dictionaries.

<div data-fill-with="grammar-Partial"></div>
Expand Down Expand Up @@ -7756,9 +7758,7 @@ the object (or its prototype chain) correspond to [=dictionary members=].
running the following algorithm (where |D| is the [=dictionary type=]):

1. If <a abstract-op>Type</a>(|esDict|) is not Undefined, Null or Object, then [=ECMAScript/throw=] a {{ECMAScript/TypeError}}.
1. Let |idlDict| be an empty dictionary value of type |D|;
every [=dictionary member=]
is initially considered to be [=not present=].
1. Let |idlDict| be an empty [=ordered map=].
domenic marked this conversation as resolved.
Show resolved Hide resolved
1. Let |dictionaries| be a list consisting of |D| and all of |D|’s [=inherited dictionaries=],
in order from least to most derived.
1. For each dictionary |dictionary| in |dictionaries|, in order:
Expand All @@ -7774,13 +7774,12 @@ the object (or its prototype chain) correspond to [=dictionary members=].
</dl>
1. If |esMemberValue| is not <emu-val>undefined</emu-val>, then:
1. Let |idlMemberValue| be the result of [=converted to an IDL value|converting=] |esMemberValue| to an IDL value whose type is the type |member| is declared to be of.
1. Set the dictionary member on |idlDict| with key name |key| to the value |idlMemberValue|. This dictionary member is considered to be [=present=].
1. [=map/Set=] |idlDict|[|key|] to |idlMemberValue|.
1. Otherwise, if |esMemberValue| is <emu-val>undefined</emu-val> but |member| has a [=dictionary member/default value=], then:
1. Let |idlMemberValue| be |member|’s default value.
1. Set the dictionary member on |idlDict| with key name |key| to the value |idlMemberValue|. This dictionary member is considered to be [=present=].
1. Otherwise, if |esMemberValue| is
<emu-val>undefined</emu-val> and |member| is a
[=required dictionary member=], then throw a {{ECMAScript/TypeError}}.
1. [=map/Set=] |idlDict|[|key|] to |idlMemberValue|.
1. Otherwise, if |esMemberValue| is <emu-val>undefined</emu-val> and |member| is
[=dictionary member/required=], then throw a {{ECMAScript/TypeError}}.
1. Return |idlDict|.
</div>

Expand All @@ -7799,10 +7798,13 @@ up on the ECMAScript object are not necessarily the same as the object’s prope
1. For each dictionary |dictionary| in |dictionaries|, in order:
1. For each dictionary member |member| declared on |dictionary|, in lexicographical order:
1. Let |key| be the [=identifier=] of |member|.
1. If the dictionary member named |key| is [=present=] in |V|, then:
1. Let |idlValue| be the value of |member| on |V|.
1. If |V|[|key|] [=map/exists=], then:
1. Let |idlValue| be |V|[|key|].
1. Let |value| be the result of [=converted to an ECMAScript value|converting=] |idlValue| to an ECMAScript value.
1. Perform [=!=] <a abstract-op>CreateDataProperty</a>(|O|, |key|, |value|).

<p class="note">Recall that if |member| has a [=dictionary member/default value=],
then |key| will always [=map/exist=] in |V|.</p>
1. Return |O|.
</div>

Expand Down