archives

« Bugzilla Issues Index

#2884 — for-of and Array.from treat strings as iterable; nothing else does


Features using the `for-of` syntax consistently use ToObject, which means strings are iterable. For example, in "12.2.4.2.4 Runtime Semantics: ComprehensionComponentEvaluation":
> 3. Let obj be ToObject(exprValue).
> 4. ReturnIfAbrupt(obj).
> 5. Let keys be GetIterator(obj).

Array.from also uses ToObject() on its argument.

But in "12.2.4.1.2 Runtime Semantics: ArrayAccumulation":
> 4. If Type(spreadObj) is not Object, then throw a TypeError exception.
> 5. Let iterator be GetIterator(spreadObj).

Incidentally, step 4 is technically redundant there, as GetIterator(x) step 2 throws a TypeError if x is primitive.


The ES6 spec is missing a high-level prose phrase that can be used in algorithms and means "now use ES6 iteration".

It could define one; here's how it might look, using Array.from step 8 as an example, (modified lines marked with !):
> 8. If usingIterator is not undefined, then
> a. If IsConstructor(C) is true, then
> i. Let A be the result of calling the [[Construct]] internal method of C with an empty argument list.
> b. Else,
> i. Let A be ArrayCreate(0).
> c. ReturnIfAbrupt(A).
> d. Let k be 0.
>! e. Repeat, for each value nextValue produced by iterating over items
> i. Let Pk be ToString(k).
> ii. If mapping is true, then
> 1. Let mappedValue be the result of calling the [[Call]]
> internal method of mapfn with T as thisArgument and
> (nextValue, k) as argumentsList.
> 2. ReturnIfAbrupt(mappedValue).
> iii. Else, let mappedValue be nextValue.
> iv. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
> v. ReturnIfAbrupt(defineStatus).
> vi. Increase k by 1.
>! f. Let putStatus be Put(A, "length", k, true).
>! g. ReturnIfAbrupt(putStatus).
>! h. Return A.

I think this would making mean adding a note in "5.2 Algorithm Conventions" that says something like:

A step in an algorithm that says "Repeat for each value x produced by
iterating through y," followed by a list of substeps, means the same
thing as these steps:

1. Let iterator be GetIterator(y).
2. ReturnIfAbrupt(iterator).
3. Let x be NextValue(iterator).
4. ReturnIfAbrupt(x).
5. Repeat, until x is **empty**
a. Perform the substeps listed.
b. Set x to NextValue(iterator).
c. ReturnIfAbrupt(x).

with NextValue appropriately defined elsewhere. ReturnIfAbrupt is already defined as a sort of macro-expansion, so this should work.

I think other specifications (particularly WebIDL) would love to have convenient-but-precise language for this. But quite apart from any external benefit, it would make the ES6 algorithms a lot easier to read.


In JS code an expression like:
"abc"[Symbol.iterator]
is equivalent to
Object("abc")[Symbol.iterator]

(see 6.2.3.1)

So, in generally strings should act as Iterables. In looks like I need to carefully review uses of GetIterator and add use ToObject appropriately for the supposedly Iterable values.

However, TC39 explicitly decided that they didn't want to ToObject the source value or default values of destructing. For example:

{replace, slice} = ""; //this throws becase RHS is not an object

So, does this rule also apply to the spread operator in array leterals? Maybe it shouldn't

let codepoints = [...someString];

seems like a very nice way to parse a string into its individual code points.


fixed in rev26 editor's draft

Strings are treated as iterable by:
spread, yield *, comprehensions, Set/WeakSet(iterable)


in rev26