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

Require options to be an object #480

Open
sffc opened this issue Jul 16, 2020 · 26 comments
Open

Require options to be an object #480

sffc opened this issue Jul 16, 2020 · 26 comments
Labels
c: spec Component: spec editorial issues editorial Involves an editorial fix s: help wanted Status: help wanted; needs proposal champion s: in progress Status: the issue has an active proposal
Milestone

Comments

@sffc
Copy link
Contributor

sffc commented Jul 16, 2020

Currently, we coerce the options argument to an object by calling ToObject. @justingrant suggested in tc39/proposal-temporal#693 that we should be stricter and throw instead.

I don't think we should change older Intl objects, due to backwards compatibility, but we can adopt this pattern in new objects.

@sffc sffc added c: meta Component: intl-wide issues s: discuss Status: TG2 must discuss to move forward labels Jul 16, 2020
@anba
Copy link
Contributor

anba commented Jul 16, 2020

If there's agreement to perform this change, we should definitely also try to apply it to the existing Intl constructors. And only if we detect any web-compat issues, back it out for the affected constructors. Similar to how we managed to restrict the "IntlLegacyConstructedSymbol" hack to just Intl.NumberFormat and Intl.DateTimeFormat, but not Intl.Collator.

ptomato added a commit to tc39/proposal-temporal that referenced this issue Aug 3, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
ptomato added a commit to tc39/proposal-temporal that referenced this issue Aug 3, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
@ptomato
Copy link
Contributor

ptomato commented Aug 3, 2020

There's a way to make this change non-normatively as well. We could change the definition of GetOption but make sure to use ToObject first on all objects passed to existing uses of GetOption. That way, the definition of GetOption could move into 262 when Temporal does, but the behaviour in Intl could remain unchanged.

I'll prepare a PR with both the editorial and normative changes, split into two commits, for discussion.

ptomato added a commit to ptomato/ecma402 that referenced this issue Aug 3, 2020
This changes the behaviour of GetOption, but it has no effect on
anything currently in the spec, because currently all values that
GetOption sees are the output of either the ToObject or ObjectCreate
operations. Therefore the change can be considered editorial.

This is in order to later eliminate the boxing behaviour of ToObject in
future Intl objects (and in current ones where there is no web-compat
issue) and to be able to move GetOption into ECMA-262 in sync with
Temporal.
ptomato added a commit to ptomato/ecma402 that referenced this issue Aug 3, 2020
This changes all uses of GetOption to accept undefined and objects, and
throw a TypeError on all other types. This is different behaviour from
what was previously specified.

As an example of what will change, previously, if user code passed a
number as an options argument (`new Intl.DateTimeFormt('en', 3)`) the
number would be boxed into a Number object which would then be queried
for a property which it didn't have. This overwhelmingly indicates a bug
in user code, so after this change, that code snippet would throw a
TypeError.

(This commit is for the purpose of discussion. Some or all of these
changes should be removed if they result in web-compat issues.)
ptomato added a commit to ptomato/ecma402 that referenced this issue Aug 3, 2020
This changes all uses of GetOption to accept undefined and objects, and
throw a TypeError on all other types. This is different behaviour from
what was previously specified.

As an example of what will change, previously, if user code passed a
number as an options argument (`new Intl.DateTimeFormt('en', 3)`) the
number would be boxed into a Number object which would then be queried
for a property which it didn't have. This overwhelmingly indicates a bug
in user code, so after this change, that code snippet would throw a
TypeError.

(This commit is for the purpose of discussion. Some or all of these
changes should be removed if they result in web-compat issues.)
@ptomato
Copy link
Contributor

ptomato commented Aug 3, 2020

OK, this is in #493, one non-normative commit followed by a normative commit.

@sffc
Copy link
Contributor Author

sffc commented Aug 13, 2020

Another option, which I mentioned in the meeting:

Keep GetOption defined as it is now, and move it to 262. In 262, make new abstract operations as follows:

<emu-clause id="sec-normalizeoptionsobject" aoid="NormalizeOptionsObject">
  <h1>NormalizeOptionsObject ( _options_, _legacyMode_ )</h1>
  <p>
    The abstract operation NormalizeOptionsObject massages _options_ into an Object to be subsequently passed to GetOption. _legacyMode_ should be *false* except in contexts where it is required for backwards compatibility.
  </p>
  <emu-alg>
    1. If _options_ is *undefined*, then
      1. Return ObjectCreate(*null*).
    1. If Type(_options_) is Object, then
      1. Return _options_.
    1. If _legacyMode_, then
      1. Return ToObject(_options_).
    1. Throw a *TypeError* exception.
  </emu-alg>
</emu-clause>

Change the 402 call sites as follows:

<!-- Current 4 lines, present in several places, preceding calls to GetOption -->
1. If _options_ is *undefined*, then	
  1. Let _options_ be ObjectCreate(*null*).	
1. Else,	
  1. Let _options_ be ? ToObject(_options_).

<!-- Replace with: -->
1. Let _options_ be NormalizeOptionsObject(_options_, true).

All new callers of NormalizeOptionsObject should have legacy mode disabled. We would enable it for the existing 402 call sites only so that the change is purely editorial.

@jswalden @FrankYFTang @gibson042 @leobalter

@ljharb
Copy link
Member

ljharb commented Aug 13, 2020

Rather than adding a "legacy mode" into 262, why not have an operation in 402 that calls NormalizeOptionsObject, and if it's abrupt, wraps the original value in ToObject?

@sffc
Copy link
Contributor Author

sffc commented Aug 13, 2020

If we don't want the legacy mode in 262, we could do:

<!-- 262 -->
<emu-clause id="sec-normalizeoptionsobject" aoid="NormalizeOptionsObject">
  <h1>NormalizeOptionsObject ( _options_ )</h1>
  <p>
    The abstract operation NormalizeOptionsObject massages _options_ into an Object to be subsequently passed to GetOption. It throws a TypeError if _options_ is not undefined and not an Object.
  </p>
  <emu-alg>
    1. If _options_ is *undefined*, then
      1. Return ObjectCreate(*null*).
    1. If Type(_options_) is Object, then
      1. Return _options_.
    1. Throw a *TypeError* exception.
  </emu-alg>
</emu-clause>

<!-- 402 -->
<emu-clause id="sec-normalizeoptionsobjectlegacy" aoid="NormalizeOptionsObjectLegacy">
  <h1>NormalizeOptionsObjectLegacy ( _options_ )</h1>
  <p>
    The abstract operation NormalizeOptionsObjectLegacy massages _options_ into an Object to be subsequently passed to GetOption. It does not throw an exception, instead making a best-effort attempt at coercing _options_ to an Object. Newer spec code should use ECMA-262's NormalizeOptionsObject instead.
  </p>
  <emu-alg>
    1. If _options_ is *undefined*, then
      1. Return ObjectCreate(*null*).
    1. If Type(_options_) is Object, then
      1. Return _options_.
    1. Return ToObject(_options_).
  </emu-alg>
</emu-clause>

@ptomato
Copy link
Contributor

ptomato commented Aug 24, 2020

NormalizeOptionsObject and NormalizeOptionsObjectLegacy seem reasonable to me. To get things moving, I would propose adding them both to the 402 spec, then we can later move NormalizeOptionsObject and GetOption into 262?

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Aug 27, 2020

I support @ptomato's suggestion in #480 (comment) and @sffc proposal in #480 (comment)

ptomato added a commit to ptomato/ecma402 that referenced this issue Aug 27, 2020
In order to later be able to move GetOption into ECMA-262 in sync with
Temporal, we add NormalizeOptionsObject and NormalizeOptionsObjectLegacy
as abstract operations.

NormalizeOptionsObject and GetOption are intended to be moved into
ECMA-262 at the same time, while NormalizeOptionsObjectLegacy is
intended to stay in ECMA-402. NormalizeOptionsObjectLegacy describes the
somewhat unexpected boxing behaviour when passing a primitive as the
options parameter in a formatter constructor. This behaviour may need to
be preserved for web-compatibility reasons, but new Intl objects going
forward should use the NormalizeOptionsObject behaviour.
@ptomato
Copy link
Contributor

ptomato commented Aug 27, 2020

I've updated the PR so that it doesn't make any normative changes. Please take a look when you have time!

ptomato added a commit to tc39/proposal-temporal that referenced this issue Aug 27, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
ptomato added a commit to ptomato/ecma402 that referenced this issue Aug 31, 2020
In order to later be able to move GetOption into ECMA-262 in sync with
Temporal, we add NormalizeOptionsObject and NormalizeOptionsObjectLegacy
as abstract operations.

NormalizeOptionsObject and GetOption are intended to be moved into
ECMA-262 at the same time, while NormalizeOptionsObjectLegacy is
intended to stay in ECMA-402. NormalizeOptionsObjectLegacy describes the
somewhat unexpected boxing behaviour when passing a primitive as the
options parameter in a formatter constructor. This behaviour may need to
be preserved for web-compatibility reasons, but new Intl objects going
forward should use the NormalizeOptionsObject behaviour.
ptomato added a commit to tc39/proposal-temporal that referenced this issue Sep 1, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
ptomato added a commit to tc39/proposal-temporal that referenced this issue Sep 1, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
ptomato added a commit to tc39/proposal-temporal that referenced this issue Sep 2, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
ptomato added a commit to tc39/proposal-temporal that referenced this issue Sep 3, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
ptomato added a commit to tc39/proposal-temporal that referenced this issue Sep 7, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
Ms2ger pushed a commit to tc39/proposal-temporal that referenced this issue Sep 10, 2020
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
ptomato added a commit to ptomato/ecma402 that referenced this issue Sep 10, 2020
In order to later be able to move GetOption into ECMA-262 in sync with
Temporal, we add NormalizeOptionsObject and NormalizeOptionsObjectLegacy
as abstract operations.

NormalizeOptionsObject and GetOption are intended to be moved into
ECMA-262 at the same time, while NormalizeOptionsObjectLegacy is
intended to stay in ECMA-402. NormalizeOptionsObjectLegacy describes the
somewhat unexpected boxing behaviour when passing a primitive as the
options parameter in a formatter constructor. This behaviour may need to
be preserved for web-compatibility reasons, but new Intl objects going
forward should use the NormalizeOptionsObject behaviour.
leobalter pushed a commit that referenced this issue Jan 12, 2021
In order to later be able to move GetOption into ECMA-262 in sync with
Temporal, we add GetOptionsObject and CoerceOptionsToObject as abstract
operations.

GetOptionsObject and GetOption are intended to be moved into ECMA-262 at
the same time, while CoerceOptionsToObject is intended to stay in
ECMA-402. CoerceOptionsToObject describes the somewhat unexpected boxing
behaviour when passing a primitive as the options parameter in a formatter
constructor. This behaviour may need to be preserved for web-compatibility
reasons, but new Intl objects going forward should use the
GetOptionsObject behaviour.
@sffc sffc added c: spec Component: spec editorial issues editorial Involves an editorial fix s: help wanted Status: help wanted; needs proposal champion and removed c: meta Component: intl-wide issues s: discuss Status: TG2 must discuss to move forward labels Feb 11, 2021
@sffc sffc added this to the ES 2021 milestone Feb 11, 2021
@sffc
Copy link
Contributor Author

sffc commented Feb 11, 2021

2021-02-11 discussion: https://github.com/tc39/ecma402/blob/master/meetings/notes-2021-02-11.md#require-options-to-be-an-object-480

We agreed to adopt the new stricter GetOptions behavior in newer APIs, but not to change older APIs to avoid breaking the web with what would most likely be benign errors. We should document this decision by ensuring that new constructors use the new behavior.

@ljharb
Copy link
Member

ljharb commented Feb 11, 2021

Is it not worth some telemetry to figure out if it would break the web?

@sffc
Copy link
Contributor Author

sffc commented Feb 11, 2021

Is it not worth some telemetry to figure out if it would break the web?

I'd be happy if someone can volunteer to do this, but from a prioritization standpoint I don't want this to delay more urgent issues that are on our backlog.

@FrankYFTang
Copy link
Contributor

2021-02-11 discussion: https://github.com/tc39/ecma402/blob/master/meetings/notes-2021-02-11.md#require-options-to-be-an-object-480

We agreed to adopt the new stricter GetOptions behavior in newer APIs, but not to change older APIs to avoid breaking the web with what would most likely be benign errors. We should document this decision by ensuring that new constructors use the new behavior.

Could you clearly define which one are part of the "newer APIs" and which one are part of the "older APIs"? The word "newer" and "older" is very ambiguous here.

@gibson042
Copy link
Contributor

Any API introduced to the specification from this point forward, minus possible exceptions for anything that is already mature enough for that change itself to present a risk.

@sffc
Copy link
Contributor Author

sffc commented Feb 12, 2021

I filed #537 to track updating new ES 2021 proposals.

@FrankYFTang
Copy link
Contributor

FYI v8 tracking https://bugs.chromium.org/p/v8/issues/detail?id=11466
test262 tracking tc39/test262#2950

@Constellation
Copy link
Member

JSC change FYI WebKit/WebKit@51b6659

@sffc
Copy link
Contributor Author

sffc commented Mar 9, 2021

Given that #538 and tc39/proposal-intl-segmenter#135 are merged, is there anything remaining to do on this issue?

@gibson042
Copy link
Contributor

Yes: #480 (comment)

We should document this decision by ensuring that new constructors use the new behavior.

I'm working on similar changes now, and do expect this to close soon.

@sffc sffc modified the milestones: ES 2021, ES 2022 Mar 22, 2021
ptomato added a commit to js-temporal/temporal-polyfill that referenced this issue May 6, 2021
See also: tc39/ecma402#480

Co-Authored-By: Philip Chimento <pchimento@igalia.com>
@romulocintra romulocintra added the s: in progress Status: the issue has an active proposal label Sep 27, 2021
@sffc sffc modified the milestones: ES 2022, ES 2023 Jun 1, 2022
@sffc sffc moved this to Previously Discussed in ECMA-402 Meeting Topics Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: spec Component: spec editorial issues editorial Involves an editorial fix s: help wanted Status: help wanted; needs proposal champion s: in progress Status: the issue has an active proposal
Projects
Archived in project
Development

No branches or pull requests

12 participants