archives

« Bugzilla Issues Index

#1489 — 13.4 Generator runtime semantics: capture of "this"


Hi,

Given:

function* foo() { "use strict"; yield this; }

We should be able to do:

function bar(x) { return foo.call(x).next().value === x; }
bar(100) // => true, for any argument passed

However the semantics as given would seem to fail for:

bar(foo())

because GeneratorBody is defined as starting "with the _this_ value" and using it as the generator object if it has a [[GeneratorState]] property.

Suggestion: only use the "this" value if called as a constructor.

Andy


Indeed! I think I understand how to fix this, but first let's talk about what the behavior should be for various use cases. Here is what I think we should be supporting. Let me know if you think anything in the following analysis is bogus:

Consider the two direct ways to define generator functions/methods:

function * foo (){yield this}; //looks like constructor, this is the new object

class Bar {
*mfoo() {yield this)} //looks like a method, this is the this obj of method call
}

Basic concept, if a generator function is invoked via new operator or as a standalone factory function, then 'this' within its body refers to the newly allocated generator object. If a generator function is invoked as a method of object X then 'this' within the its body refers to X.

yield new foo; // yields newly allocated generator, this is obj alloc'd with @@create

yield foo(); // yields newly allocated generator, this is obj alloc'd by evaluate body

let b = new Bar();

yield b.mfoo() // yields b, this bound by method call

yield new b.mfoo() // TypeError, concise methods are not constructors

//unusual situations
yield foo.call(Generator[@@create](); //same as: yield new foo

yield (undefined,b.mfoo)() //same as: yield foo() //yields new generator obj

var f = b.mfoo;
yield f() //same as above
yield new f() // TypeError, mfoo is not a constructor

b.foo = foo;
yield b.foo() //same as: yield b.mfoo() //yields b

yield foo.call(b) //same as above, method on b

Number.prototype.mfoo = foo;
yield 42.mfoo() // yields 42 //same as foo.call(42), method on 42

var g = foo(); //g is a gen generator object
yield foo.call(g) //yields g //method on g


Created attachment 36
first proposed update to fix bug


Hi,

Basics first:

(In reply to comment #1)
> Basic concept, if a generator function is invoked via new operator or as a
> standalone factory function, then 'this' within its body refers to the newly
> allocated generator object.

That's an interesting interpretation :) It's not the one I had, fwiw. My interpretation was that "this" is simply another argument to the generator, determined by the call site, and is captured by the suspension, like any other argument.

Admittedly, I haven't quite gotten the tao of javascript yet. Why would you want the "this" to have the above behavior in standalone generator functions? It seems to me that the usual JS rules for this could apply just as well.

Andreas, do you have any thoughts on this?

Andy


Hm, of course, that's the behaviour I would expect in the case you use 'new'. But when you call a generator function directly? Do you really want to break the property that

f(args)

is equivalent to

f.call(undefined, args)

?


(In reply to comment #4)
> Hm, of course, that's the behaviour I would expect in the case you use 'new'.
> But when you call a generator function directly? Do you really want to break
> the property that
>
> f(args)
>
> is equivalent to
>
> f.call(undefined, args)
>
> ?

Yes, was there something in my examples that suggest that this wasn't the case?

I didn't explicitly give this use case but,

yield foo.call(undefined)
and
yield foo()
should behave identically and in these examples should both yield a newly created generator object.


Ah, I see the example now.

Unfortunately, it seems that having generator calls behave like you suggest requires either breaking the equivalence I mentioned, or breaking the one that Andy gave (and analogously the property that you always get the global object in sloppy mode when you call without a receiver). There doesn't seem to be a way around it. Your preference seems to be the latter, but I think neither is desirable.

Can you explain what motivates your suggestion? This semantics does not allow a generator function to assume that 'this' actually is the generator object. So it's kind of useless in that regard.

Neither can a generator function assume that 'this' is the actual receiver, in analogy to ordinary functions. That causes a needless discontinuity if you e.g. were to refactor a sequential function into a task.

So it seems like lose-lose to me.


Swapping this thread back in.

I'm actually now feeling closer to Andy's original position that the simplest way to think about the this binding in the generator body is whatever was originally passed as the this value. However, if we go that route, I think we should also make all generators non-newable. That's already the case for generator functions defined as concise methods. Basically, it doesn't make sense to code a generator body as if it was a constructor body where this is bound to the newly allocated object. Allowing new suggests that you should be able to do that. Disallowing new should should re-enforce that it isn't a now constructor body.

This is consistent with the original Firefox generator implementations which did not allow use of new. I kinda liked allowing new because using new with a generator reinforced the concept that each invocation of a generator function was creating and returning a new generator object. However, I think everything will be conceptually simpler if all generators functions are thought of as non-newable factory functions.

I'm tempted to say that generator functions bodies should always be strict so we don't have to deal with undefined/null this turning into the global object. But it probably isn't any more of a hazard here then it is for regular functions.

Thoughts?


Good morning! Thanks for taking the time to look at this; I do not envy your backlog :)

For what it's worth: I have no opinion about allowing generators to be newable or not. I was surprised when you first suggested it, but when implementing it, everything fell out as it should without having to specially add support for new. So by that one empirical measure, "new" makes sense; though I agree that coding the body of a generator as if it were a constructor makes less sense.

Basically (again FWIW) I don't see the need for special language regarding "this" related to generators. If the spec simply refers to the treatment of functions, then all is good. "new g()" is a little weird but its meaning is determined by the existing concepts.


I'm also indifferent wrt 'new'. I'm fine with disallowing it, but there also doesn't appear to be a strong reason to do so. I'd suggest going with whatever is most regular, i.e. simplest.


Yes, newable generator functions just falls out of a consistent application of the basic language concepts.

I'm going to leave them newable, But I'm going to remove line 4.c and 4.c.i (see attachment) which rebound this to be the factory allocated generator object in some cases. This leaves the this binding as originally established, as Andy expect. Simple, no magic...

This basically means that within a generator function body "this" is really only predictably useful when the generator function is invoked as a method on an object. I think that is a plausibly simple concept.


fixed in rev16 editor's draft


fixed in rev16 draft. July 15, 2013