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

Callable classes through proxies #402

Closed
nzakas opened this Issue Feb 22, 2016 · 8 comments

Comments

Projects
None yet
3 participants
@nzakas

nzakas commented Feb 22, 2016

Digging through the spec, I found that there is [[FunctionKind]] attribute that is set to classConstructor for class constructors, and it appears the sole purpose for this is to throw an error when you attempt to call a class constructor without new (ref: http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist).

I know the decision was made to not have class constructors callable, but prior to reading this part of the spec, I had expected this to work:

class Person {
    constructor(name) {
        this.name = name;
    }
}

let PersonProxy = new Proxy(Person, {
        call: function(trapTarget, thisArg, argumentList) {
            return new trapTarget(...argumentList);
        }
    });


var me = Person("Nicholas");

Of course, this won't work because [[FunctionKind]] is still set to "classConstructor" and therefore throws an error.

My question is: would it at all be feasible to allow proxies to be used in this way? It seems like the language could behave the same way by ensuring that class constructors don't have a [[Call]] or has a [[Call]] that throws by default. That would the allow proxies to override that behavior and would eliminate the special case for class constructors.

Just curious if that's remotely feasible?

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Feb 22, 2016

Member

It's feasible, sure, but I'm not sure it's desirable :) We have deliberately decided that a proxy only has a [[call]] and [[construct]] slot if the target has a [[call]] and [[construct]] slot. Is the behavior you want desirable because it allows you to implement call behavior using a proxy? If so I think waiting for a proposal to fix this is better than making some change to proxy invariants in the near term.

Member

bterlson commented Feb 22, 2016

It's feasible, sure, but I'm not sure it's desirable :) We have deliberately decided that a proxy only has a [[call]] and [[construct]] slot if the target has a [[call]] and [[construct]] slot. Is the behavior you want desirable because it allows you to implement call behavior using a proxy? If so I think waiting for a proposal to fix this is better than making some change to proxy invariants in the near term.

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Feb 22, 2016

Is the behavior you want desirable because it allows you to implement call behavior using a proxy?

Yes. I guess the overall question is why lock down the class constructor so tightly? If a class constructor has no default [[Call]] or one that throws, then the current behavior (of throwing when new isn't called, and also the way proxies work) is maintained, and we now have a workaround.

I know callable class constructors are coming, I'm just unclear on why class constructors are so locked down right now when proxies let us override so many other default behaviors.

So, please don't take this as a proposal, just more of a question, is there a reason this is such a special case?

nzakas commented Feb 22, 2016

Is the behavior you want desirable because it allows you to implement call behavior using a proxy?

Yes. I guess the overall question is why lock down the class constructor so tightly? If a class constructor has no default [[Call]] or one that throws, then the current behavior (of throwing when new isn't called, and also the way proxies work) is maintained, and we now have a workaround.

I know callable class constructors are coming, I'm just unclear on why class constructors are so locked down right now when proxies let us override so many other default behaviors.

So, please don't take this as a proposal, just more of a question, is there a reason this is such a special case?

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Feb 22, 2016

Member

Class constructor is locked down tightly to be as future-compatible as possible with the eventual call-constructor proposal. You could imagine having proxies work around this issue, but it seems like a desirable aspect of proxies that they inherit the callability and constructability of the target. There is probably a similar future compatibility argument to be made as well. I'm not sure if this is a security-critical invariant of proxies so perhaps someone else can chime in on that front.

Member

bterlson commented Feb 22, 2016

Class constructor is locked down tightly to be as future-compatible as possible with the eventual call-constructor proposal. You could imagine having proxies work around this issue, but it seems like a desirable aspect of proxies that they inherit the callability and constructability of the target. There is probably a similar future compatibility argument to be made as well. I'm not sure if this is a security-critical invariant of proxies so perhaps someone else can chime in on that front.

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Feb 23, 2016

Digging into this further, I see that this falls into the note on http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist:

A Proxy exotic object only has a [[Call]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Call]] internal method.

So that explains why class constructors throw an error in this case.

Thanks!

nzakas commented Feb 23, 2016

Digging into this further, I see that this falls into the note on http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist:

A Proxy exotic object only has a [[Call]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Call]] internal method.

So that explains why class constructors throw an error in this case.

Thanks!

@nzakas nzakas closed this Feb 23, 2016

@anba

This comment has been minimized.

Show comment
Hide comment
@anba

anba Feb 23, 2016

Contributor

Class constructors have a [[Call]] internal method. So the example code should work in a ES2015/2016 compatible engine (after fixing the Person -> PersonProxy typo in var me = Person("Nicholas");).

Contributor

anba commented Feb 23, 2016

Class constructors have a [[Call]] internal method. So the example code should work in a ES2015/2016 compatible engine (after fixing the Person -> PersonProxy typo in var me = Person("Nicholas");).

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Feb 23, 2016

Member

@anba of course you're right! How is it all impls have this same bug???

@nzakas, your scenario should work.

Member

bterlson commented Feb 23, 2016

@anba of course you're right! How is it all impls have this same bug???

@nzakas, your scenario should work.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Feb 23, 2016

Member

I take it back, the reason it doesn't work is the call trap is called apply not call. Once renamed this works fine across all implementations.

Member

bterlson commented Feb 23, 2016

I take it back, the reason it doesn't work is the call trap is called apply not call. Once renamed this works fine across all implementations.

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Feb 24, 2016

Oh wow, I was doomed by typos. Thanks!

nzakas commented Feb 24, 2016

Oh wow, I was doomed by typos. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment