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

Expand R&T support across methods that accept objects and arrays #264

Merged
merged 16 commits into from
Oct 11, 2022

Conversation

nicolo-ribaudo
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo commented Oct 14, 2021

I didn't add support for:

  • Reflect.*, since they already disallow primitives even if it would make sense to use them (e.g. Reflect.get("str", 0)).
  • Proxy handler (new Proxy(target, #{})), since it's meant to contain functions so a record would only work as an empty handler.
  • JSON.stringify's replacer array, just to avoid conflicts with Update boxes to work in SES environments #257: I'll do it in a separate PR in the future EDIT: done. There are also a bunch of bugs in the current JSON implementation, where we pass R/T to AOs that expects objects.

Closes #107

This PR should be reviewed commit-by-commit. Most algorithms in the misc-updates.html file are copied from the existing spec (except for InitializeTypedArrayFromTuple and UnwrapIteratorResult): focus on the <ins> and <del> tags.

@nicolo-ribaudo
Copy link
Member Author

Do you think that these should be valid?

new Map([ #{ 0: "x", 1: "y", length: 2 } ]);
new Map([ #{ 0: "x", 1: "y", length: 1 } ]);
fn.apply(thisArg, #{ 0: "x", 1: "y", length: 2 });

@mhofman
Copy link
Member

mhofman commented Oct 14, 2021

Do you think that these should be valid?

new Map([ #{ 0: "x", 1: "y", length: 2 } ]);
new Map([ #{ 0: "x", 1: "y", length: 1 } ]);
fn.apply(thisArg, #{ 0: "x", 1: "y", length: 2 });

AddEntriesFromIterable doesn't currently require a length so I'm not sure a check should be added now.

Since we're adding support for tuple, and these already support array like objects, why not add array like records support as well?

@ljharb
Copy link
Member

ljharb commented Oct 14, 2021

Most places only accept an arraylike, or only an iterable; Array.from is one of the only ones that accepts both. new Map only accepts an iterable - being arraylike is irrelevant. Map and Set should not accept Records, since they're not iterables.

@mhofman
Copy link
Member

mhofman commented Oct 14, 2021

AddEntriesFromIterable only cares about the entries of the iterable being an object with a 0 and 1 property.

d. If Type(nextItem) is not Object, then
  i. Let error be ThrowCompletion(a newly created TypeError object).
  ii. Return ? IteratorClose(iteratorRecord, error).
e. Let k be Get(nextItem, "0").
f. IfAbruptCloseIterator(k, iteratorRecord).
g. Let v be Get(nextItem, "1").
h. IfAbruptCloseIterator(v, iteratorRecord).

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

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

I stopped partway through, but overall it seems like it'd be better to avoid the frequent object coercion if possible

spec/abstract-operations.html Outdated Show resolved Hide resolved
spec/miscellaneous-updates.html Outdated Show resolved Hide resolved
spec/miscellaneous-updates.html Outdated Show resolved Hide resolved
Comment on lines 100 to 104
1. <ins>If Type(_nextItem_) is Tuple, then</ins>
1. <ins>Set _nextItem_ to ! ToObject(_nextItem_).</ins>
1. <ins>Else</ins> if Type(_nextItem_) is not Object, then
1. Let _error_ be ThrowCompletion(a newly created *TypeError* object).
1. Return ? IteratorClose(_iteratorRecord_, _error_).
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't this just work the same without the object conversion? ie "if it's not tuple or object", and then later, the Get will Just Work?

Copy link
Member Author

@nicolo-ribaudo nicolo-ribaudo Oct 15, 2021

Choose a reason for hiding this comment

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

Get expects an object: https://tc39.es/ecma262/#sec-get-o-p

The alternatives are:

  • Special-case records and tuples in Get (I cannot use _record_.[[Get]], since I think primitives don't have internal methods?)
  • Use GetV, which is just ToObject+Get.

(same for LengthOfArrayLike)

Copy link
Member

Choose a reason for hiding this comment

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

Special-casing Get and friends to handle records and tuples seems like a reasonable approach.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looking at the whole spec, Get() is used ~140 times on objects. We would need to call it on tuples only ~10 times, so I think it's better to use GetV when dealing with "R&T or objects", making it easier to see "this will only be called with objects, thus we can make these assumptions/optimizations" in the majority of cases while reading the spec.

Copy link
Member

Choose a reason for hiding this comment

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

What about future edits? It seems like it'd be an easy mistake to make to use Get with an R&T and have it break.

Copy link
Member Author

@nicolo-ribaudo nicolo-ribaudo Nov 19, 2021

Choose a reason for hiding this comment

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

It would be as hard/easy as it is to currently accidentally use primitive strings where we expect array-like values. When we are not sure that we have an object, we should use GetV "by defaut" in any algorithm.

Copy link
Member

Choose a reason for hiding this comment

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

i'm not sure i see that - records are object-like and tuples are array-like, conceptually, and strings being actually arraylike doesn't mean they're conceptually arraylike :-)

spec/miscellaneous-updates.html Show resolved Hide resolved
spec/miscellaneous-updates.html Show resolved Hide resolved
@ljharb
Copy link
Member

ljharb commented Oct 14, 2021

@mhofman ah, i see what you mean. yes, that's true, it seems reasonable for either Tuples or Records to be the entry objects in Map/Set/Object.fromEntries.

@acutmore

This comment was marked as resolved.

@ljharb

This comment was marked as resolved.

@@ -873,20 +873,17 @@ <h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-isconcatspreadable" type="abstract operation">
Copy link
Collaborator

Choose a reason for hiding this comment

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

For reviewers: isconcatspreadable not deleted but moved to miscellaneous-updates.html

spec/miscellaneous-updates.html Show resolved Hide resolved
spec/miscellaneous-updates.html Show resolved Hide resolved
@michaelficarra
Copy link
Member

I don't think IsConcatSpreadable should treat tuples as arrays. IsConcatSpreadable is a hook for things that are much more array-like, such as NodeLists in HTML.

@Jack-Works
Copy link
Member

IsConcatSpreadable is a hook for things that are much more array-like

Isn't tuple array-like?

@ljharb
Copy link
Member

ljharb commented Jul 7, 2022

It is 100% arraylike; i fail to see why it wouldn’t qualify.

@acutmore
Copy link
Collaborator

acutmore commented Jul 7, 2022

I don't think IsConcatSpreadable should treat tuples as arrays. IsConcatSpreadable is a hook for things that are much more array-like, such as NodeLists in HTML.

Hi @michaelficarra , would you be able to expand on this? What would your expectation be for the following:

let a =  ['a1', 'a2'];
let b = #['b1', 'b2'];

a.concat(a); // ['a1', 'a2', 'a1', 'a2'];
a.concat(b); // ?
b.concat(a); // ?
b.concat(b); // ?

@michaelficarra
Copy link
Member

michaelficarra commented Jul 7, 2022

It is 100% arraylike; i fail to see why it wouldn’t qualify.

Yes array-like in that it has a numeric length and numeric indices, but not meeting the bar for concat. Notice that general array-likes like { length: 0 } and arguments objects do not get spread by concat.

What would your expectation be for the following:

let a =  ['a1', 'a2'];
let b = #['b1', 'b2'];

a.concat(a); // ['a1', 'a2', 'a1', 'a2']
a.concat(b); // ['a1', 'a2', #['b1', 'b2']]
b.concat(a); // whatever you like
b.concat(b); // #['b1', 'b2', 'b1', 'b2'];

@ljharb
Copy link
Member

ljharb commented Jul 7, 2022

I agree that concat doesn’t spread all arraylikes - but Tuple is far closer to an array than most arraylikes, as a conceptual and iterable List, with list operations on its prototype.

@michaelficarra
Copy link
Member

I agree that concat doesn’t spread all arraylikes

This is far too weak of a statement. It spreads no array-likes. It is meant to only spread arrays. For historical HTML integration reasons, we had to add the isConcatSpreadable hook, but concat spreading should never be expanded further.

@ljharb
Copy link
Member

ljharb commented Jul 7, 2022

Yes, it only spreads Arrays and NodeLists, but Tuples are also built-in list-like collections. Why wouldn’t we want to expand it further? Array concat’s flattening behavior is immensely idiomatic and useful.

@michaelficarra
Copy link
Member

Array concat’s flattening behavior is immensely idiomatic and useful.

I'd argue that concat spreading is more-often-than-not a footgun. This is why the usage pattern of a.concat([b]) came to be: fear that b may be spread.

@ljharb
Copy link
Member

ljharb commented Jul 7, 2022

That’s also why a.concat(b) is so common - the hope that if a list, it will be spread, and if not, not. Because spreading syntax also spreads strings, prior to flat existing, concat was the only way to do this, and as such it’s quite common to do so.

@acutmore
Copy link
Collaborator

acutmore commented Jul 8, 2022

There are other places where we are treating Tuples more like Arrays than 'array-like'. Namely Array.prototype.flat and JSON.stringify. A general design philosophy of Tuples is to think of them like Arrays, rather than Array likes, this can be seen in their literal syntax sharing the [ ] style and Tuple.prototype being a subset of Array.prototype.

@ljharb
Copy link
Member

ljharb commented Jul 8, 2022

Additionally, Tuple has concat, which uses IsConcatSpreadable. #[a].concat(#[b]) is and must be #[a, b], and thus, [a].concat([#b]) must be [a, b] for consistency (and because that's the only thing that makes sense).

@michaelficarra
Copy link
Member

@ljharb Nothing requires Tuple's concat to use IsConcatSpreadable.

@ljharb
Copy link
Member

ljharb commented Jul 8, 2022

I think user expectations do. We added a bunch of methods to Array just so Arrays and Tuples would be consistent. If arrays can spread Arrays and NodeLists, Tuples must be able to as well, but the same logic.

@bakkot
Copy link
Contributor

bakkot commented Jul 18, 2022

If arrays can spread Arrays and NodeLists, Tuples must be able to as well, but the same logic.

Ehhh.... NodeLists contain non-primitives pretty much by definition. I am entirely OK with Tuple.prototype.concat rejecting empty NodeLists in addition to non-empty NodeLists (which it must anyway).

@nicolo-ribaudo nicolo-ribaudo changed the title Expand support for R&T across the existing methods that accepts objects and arrays Expand R&T support across methods that accept objects and arrays Oct 11, 2022
@nicolo-ribaudo nicolo-ribaudo merged commit 94cd6e3 into tc39:main Oct 11, 2022
@nicolo-ribaudo nicolo-ribaudo deleted the rt-compatibility branch October 11, 2022 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make tuples usable as "entry" objects
10 participants