"for-in", shadowing and deleting properties.

Gareth Smith gds at doc.ic.ac.uk
Fri May 10 06:47:18 PDT 2013


Hi Allen,

Allen Wirfs-Brock <allen at wirfs-brock.com> writes:
> I think what you are doing here falls under the "newly added
> properties are not guaranteed to be visited" rule.  However, I think
> this is also one of the situations where it makes a difference on
> whether "visited" is interpreted to mean returned to the user or
> processed by this algorithm.  This is a case where "processed" was the
> intended meaning.
>
> Behind all this was the basic idea that an implementation was not
> required to keep looking for new properties (or changes of attributes)
> in objects that have already been processed by the algorithm.  With my
> generator algorithm and you test case, "x" only gets added to obj
> after the algorithm has already processed obj and moved on to proto_ob
> so the algorithm is allowed to ignore that added property and any
> shadowing effect it might of had.

Ok, cool. I think it's clear that property(1) from my previous email was
too strong. So concerning the step 6a or 7a (depending on what's on the
left of the "in") in the ES5 standard, which begins "Let P be the name
of the next property of obj whose [[Enumerable]] attribute is true.":

...I'm now thinking that this line might mean that we do have the
following guarantee:

(2) when we enter a particular iteration of the loop for(k in obj) it
must be the case that there is some obj2 in the prototype chain of obj
(so obj2===obj or obj2===obj.__proto__ and so on) such that obj2[k] is
enumerable.

Does that sound right to you?

Here's one possible implementation that would violate this property(2):

--8<---------------cut here---------------start------------->8---
function *forin(obj) {
    let processed = new Set();
    let safe_to_visit = new Array();
    let safe_count = 0;
    while (obj!==null) {
        let here = Object.getOwnPropertyNames(obj);
        for (let i=0; i<here.length; i++) {
            let name = here[i];
            if (processed.has(name)) continue;
            processed.add(name);
            let desc = Object.getOwnPropertyDescriptor(obj,name);
            if (desc && desc.enumerable) safe_to_visit[safe_count++] = name;
        }
        obj = Object.getPrototypeOf(obj);
    }
    for(let i=0; i<safe_count ; i++) {
        yield safe_to_visit[i];
    }
}
--8<---------------cut here---------------end--------------->8---

However, this implementation might fall foul of the letter of the
(ES5) standard, since the standard seems to require that the
enumerability of the property be checked immediately before executing
the loop body. What do you think?

Another possible variation on this property is:

(3) when we enter a particular iteration of the loop for(k in obj) it
must be the case that at some moment since the loop started running
obj[k] was enumerable.

I think your implementation at https://gist.github.com/allenwb/5548164
violates this property(3), since I can do:

--8<---------------cut here---------------start------------->8---
grandproto_ob = {};
Object.defineProperty(grandproto_ob, "x", {value: 1, enumerable: false})
proto_ob = {y:2, __proto__:grandproto_ob};
ob = {__proto__:proto_ob};
for (k in ob) {
    alert("ob."+k+" has value: "ob[k]);

    if(k===y) {
        Object.defineProperty(ob, "x", {value: 3, enumerable: false})
        grandproto_ob.x = 4
    }
}
--8<---------------cut here---------------end--------------->8---

I think when we run this example program, there is no point at which
ob.x is enumerable. I also think that when we run it using your
generator, we'll see "ob.x has value: 3". So I think property(3) is a
non-starter.

Perhaps if the standard allows both your generator, and also my suspect
generator above, and also allow properties to be visited in any order,
we might only be able to guarantee the following:

(4) when we enter a particular iteration of the loop for(k in obj) it
must be the case that at some moment since the loop started running
there was some obj2 in the prototype chain of obj at that time (so
obj2===obj or obj2===obj.__proto__ and so on), such that obj2[k] was
enumerable.

If that's the best we can do in the case when things are changing
underfoot, we may want to explicitly state the properties we want when
nothing has changed:

(5) when we enter a particular iteration of the loop for(k in obj):
  if
    for all obj2 in the prototype chain of obj (so obj2===obj or
    obj2===obj.__proto__ and so on)
    it is the case that the prototype of obj2 has not been mutated since
    the loop began, and it is the case that obj2[k] has not been mutated
    since the loop began
  then
    it must be the case that obj[k] is enumerable.

If property(2) above is still too strong, then do you think properties
(4) and (5) are ok? Or have I still not properly understood what you've
told me?

I notice that even if property (2) is guaranteed by the ES5 spec, the
introduction of Iterator objects in ES6 seem to make it less likely to
be guaranteed there. Indeed, if I'm reading it correctly, the
"informative algorithm" at the end of section 8.3.12 of the ES6 draft
seems to violate property (2) above.

Sorry for the length of this email, and thanks for all your help so far!

Gareth.


More information about the es-discuss mailing list