-
Notifications
You must be signed in to change notification settings - Fork 163
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
Maplike/setlike iterators shouldn't use MapIteratorPrototype/etc #1142
Comments
-1; reusing %MapIteratorPrototype% and %SetIteratorPrototype% is something all engines do, and something we intentionally designed. ES should allow this kind of layering so that other specs can vend such classes, without having to wrap things up into a [[BackingMap]]/[[BackingSet]]. |
If it's intended, that's fine with me. (The behavior in #1138 does work with the ES spec as currently written, though we didn't really intend for it to when we did the refactoring in tc39/ecma262#2045.) It's conceptually weird to me, and I'm surprised it doesn't cause problems for implementations - I would have expected implementations of Edit: Specifically, the main user-observable consequence of re-using |
It's perfectly reasonable to ask 262 to provide these affordances for layered specs; that doesn't mean we do at the moment, though. @domenic are you saying there's existing web iterators where |
I couldn't find any examples of this right now, poking around a little, though it's entirely possible I've just missed it. E.g., |
Nah I'm saying that there are web iterables that return iterators that are instances of MapIteratorPrototype and vend keys/values based off of a separate internal data store (which the IDL spec used to model as an internal %Map%) |
Hmm OK, I thought I tested this a while ago but perhaps not. Well, if all browsers are violating the spec in the same way, then perhaps I need to reconsider my position :). Please hold for more investigation and testing... |
This is tricky because there aren't too many maplikes and setlikes in web platform specs and even fewer are implemented widely. Findings so far:
Given this total lack of interop we could kind of go in any direction. My preference would be for the current spec (modulo layering fixes), i.e. use %MapIteratorPrototype% and %SetIteratorPrototype%. That would avoid telling Safari and Firefox, who implemented maplikes "correctly" (i.e. matching the spec), to do something new. Alternatives include:
|
It seems like the ideal approach is for 262 to provide affordances to make it easy for, eg, AudioParamMap to have its own iterator prototype (rather than weirdly using Map's iterator prototype). Why would it be desirable to use the iterator prototype that is exclusively intended for Maps with "not Maps"? |
Because they're maplike. |
That last option (using %MapIteratorPrototype% but not %SetIteratorPrototype%) doesn't seem very sensical; it's just specifying bugwards compat, rather than a behavior that actually makes sense.
I mean, these are Maps in the important senses; that's the whole point of being maplike. Aside from some internal impl details, authors can treat them exactly like any other map. |
right, but it's not a Maplike Iterator, it's a Map iterator. It's not intended for, designed for, or meant for Map-likes. |
Also, please note that you're incorrect in talking about what they were "intended" for; maplikes were co-designed with |
@tabatkins a Map is something that has a [[MapData]] slot, that i can @domenic thanks for the correction on the history of it; it seems telling that such a thing hasn't been discussed in committee, and nobody seemed to know about it, when we discussed refactoring these builtins, since at least 2014. |
I know you are very focused on internal slots and brands, but that does not hold for everyone, and it certainly did not hold for the ES2015 editor. At the time TC39/the Web IDL community (mostly on public-script-coord) designed Map as a contract based on a set of public methods, and thought that every class which conformed to those methods had an equal right to vend %MapIteratorPrototype% instances from those methods. |
The committee's makeup has certainly shifted; the impetus seems to currently be towards minimizing observable lookups by this implicit "contract" (which for Map is just "the constructor calls It's unfortunate that WebIDL has designed a bunch of things with an understanding that TC39 seems to not have widely (or at least, overtly) shared for the last half-decade. |
None of the alternatives under discussion here call .set etc. on Map instances. We're just discussing whether other specs are allowed to create instances of a type. The answer is clearly yes for various TC39 types such as Promise, Map, etc. And back then the answer was also clearly yes for %MapIteratorPrototype%. |
I understand that. "instances of a type" is determined in ecma-262 pretty much entirely by the presence of internal slots, which are observable for all built-in types except Error, including the iterator prototypes. |
I don't really care how you characterize type. I care whether Web IDL can create %MapIteratorPrototype%s like it does %Promise%s. The answer used to be yes. |
Sure - and my comment that you quoted in #1142 (comment) would determine whether that creation was done correctly. I think the answer is still yes, modulo the internal slots requirement - but it's a different question as to whether it's appropriate versus whether it's allowed (which it is, ofc, allowed). |
My point of using %MapIteratorPrototype% is just that, if we ever add anything to that proto (some map-specific iteration helpers?) we'd want them to be available for maplikes as well; it also defines the minimal set of methods we need for our purposes (rather than, say, the Generator prototype, which has The algos involved don't invoke the internal slots at all, except hidden in the closure argument that's constructed and passed to CreateIteratorFromClosure, which is why it seemed appropriate to reuse that algo with a custom closure. Impls might internally implement things more directly, of course, and have their Map |
I don't think any option here is likely to actually affect users in any noticeable way, so I am personally fine with any outcome here. I do think some choices might affect engines. If WebIDL stuff re-uses From a theoretical purity perspective, I think the previous WebIDL spec, where there was a That said, I do think that anything using |
To the best of my knowledge, I have maintained consistency with real Map iterators (the details should just be in how Infra specifies maps and sets to be iterated over, which I'm defining to match ES in whatwg/infra#451). |
Oh also, note:
This would be completely fine, probably ideal in fact. This change was meant to make messing around with the contents of a maplike/setlike actually doable for human spec editors, rather than requiring everyone to know and get perfectly right the precise dance of algos that manipulating ES objects requires. (For example, I was trying to write https://tabatkins.github.io/css-toggle/#dom-csstogglemap-set when I realized it would be really fiddly and annoying to both read and write, which caused me to come shave this yak first; with the new IDL stuff, the algorithm was very easy to write and is easy to understand for the reader.) |
Yeah I think you're fine currently. The main details to worry about are iteration order and coercing
To be clear, by "underlying data structure", I mean the actual C++ object which backs the thing, not the spec-level thing used to talk about it in algorithms. My thinking is that WebIDL maplikes specify types more narrowly than ES Maps do, which might lead to wanting a different backing store. This is particularly relevant if there's ever a maplike which has only a small number of possible keys, which is a place where you'd really want to back it by something other than a hashmap. But maybe this won't actually come up in practice. This is pure speculation on my part; I wanted to make sure the possible downside is recognized but I don't have any idea how likely or important it actually is. |
As we discovered, I didn't get this right; using holes (as the ES spec does) gives an observably different behavior than not doing so (as Infra currently does): if a collection is mid-iteration, and something is removed from the beginning, using holes means you'll still visit every subsequent item; not using holes means you'll "skip ahead" over one item, since they all have their indexes shifted down by one. This'll need fixing.
The default impl of the mutation methods do this, and I already have requirements on what the mutation methods have to return; adding a SHOULD requirement to coerce -0 to 0 is reasonable to add as well. Will fix.
Talking to our engineers at Chrome, it seems that our current maplike/setlike impls are all doing disparate things anyway, not sharing significant code with the ES Maps and Sets. So afaict this shouldn't be a big issue for Chrome, at least; we'll just continue using distinct code that's written to intentionally match Maps/Sets in behavior but without expectation of code-sharing. |
That's actually my concern: the problem is that re-using |
Firefox does use |
#1138 switched maplike/setlike interfaces to use Infra maps/sets, and as part of that had to redefine some text out of the ES spec, notably reusing the language of CreateMapIterator and subbing in a new closure that worked on the infra map/set rather than the ES Map/Set it previously used.
Per discussion with @bakkot and @ljharb, the ES spec doesn't, in fact, intend for this to be pluggable in quite this way; notably, apparently it's bad to create an iterator inheriting from %MapIteratorPrototype% but with a different iteration behavior. (CreateIteratorFromClosure looked pluggable, but in reality the second and third arguments should be unique per call site.)
They suggest instead that I should define a new prototype object, descended from %IteratorPrototype%, and define its methods itself; this is very short and simple to do, as shown in the definition of %MapIteratorPrototype%. (I shouldn't just use %GeneratorPrototype%, like a userland generator, because then I have to worry about
.return()
, which I don't want to worry about.)Patch incoming.
The text was updated successfully, but these errors were encountered: