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

Objects created from proxy of bound function don't get the expected prototype #1052

Open
LucaFranceschini opened this Issue Dec 20, 2017 · 26 comments

Comments

Projects
None yet
@LucaFranceschini

LucaFranceschini commented Dec 20, 2017

function Foo() { }
Foo.prototype.bar = 'baz'
const Bound = Foo.bind(42)  // just to build a bound function
    , Proxied = new Proxy(Bound, { })  // empty handler
console.log(new Proxied().bar)

This prints undefined while I expected baz. (Much) More details here. Long story short: the prototype property of the proxy object is used (which forwards to the prototype property of the bound function), which looks... weird.

More generally, I would expect new Proxy(O, { }) to behave exactly like O.

Bound functions actually do a trick, they set new.target to the original function so that everything works as expected (step 5 of this). I would expect Proxy objects to do the same, maybe here.

What is the rationale behind this choice?

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Dec 20, 2017

More generally, I would expect new Proxy(O, {}) to behave exactly like O.

Unfortunately, this expectation doesn't hold for most native O objects with internal slots, like sets, iterators, dates or maps. Bound functions are just another case. (That said, I tend to agree that something should be done on this problem).

bergus commented Dec 20, 2017

More generally, I would expect new Proxy(O, {}) to behave exactly like O.

Unfortunately, this expectation doesn't hold for most native O objects with internal slots, like sets, iterators, dates or maps. Bound functions are just another case. (That said, I tend to agree that something should be done on this problem).

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Dec 20, 2017

Member

Functions in particular, because of Function.prototype.toString, are distinguishable from Proxies. Arrays, however, are not, because of the internal isArray Proxy unwrapping. Should perhaps functions have the same unwrapping, bound or otherwise?

Member

ljharb commented Dec 20, 2017

Functions in particular, because of Function.prototype.toString, are distinguishable from Proxies. Arrays, however, are not, because of the internal isArray Proxy unwrapping. Should perhaps functions have the same unwrapping, bound or otherwise?

@claudepache

This comment has been minimized.

Show comment
Hide comment
@claudepache

claudepache Dec 20, 2017

Contributor

More generally, I would expect new Proxy(O, {}) to behave exactly like O.

Unfortunately, this expectation doesn't hold for most native O objects with internal slots

Another case where things could break, is when an algorithm relies on some identity, because identity cannot be proxied. Because of this, even proxying a random plain object may lead to surprises.

For bound constructors, the offending step mentioned in Comment 0 (step 5 of this) precisely relies on identity.

Contributor

claudepache commented Dec 20, 2017

More generally, I would expect new Proxy(O, {}) to behave exactly like O.

Unfortunately, this expectation doesn't hold for most native O objects with internal slots

Another case where things could break, is when an algorithm relies on some identity, because identity cannot be proxied. Because of this, even proxying a random plain object may lead to surprises.

For bound constructors, the offending step mentioned in Comment 0 (step 5 of this) precisely relies on identity.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Dec 20, 2017

Member

However, just like the isArray abstract operation, it could unwrap the Proxy (as long as it didn’t set the unwrapped value to be the target).

Member

ljharb commented Dec 20, 2017

However, just like the isArray abstract operation, it could unwrap the Proxy (as long as it didn’t set the unwrapped value to be the target).

@claudepache

This comment has been minimized.

Show comment
Hide comment
@claudepache

claudepache Dec 20, 2017

Contributor

Another thing broken with proxied bound functions is the instanceof operator, because the algorithm branches on the presence of the [[BoundTargetFunction]] internal slot. As a result, qux instanceof Proxied will throw instead of returning a boolean.

Having scanned the uses in the spec of the [[Bound*]] internal slots as well as the expression “bound function”, I think that only those two cases (new and instanceof) are problematic.

Contributor

claudepache commented Dec 20, 2017

Another thing broken with proxied bound functions is the instanceof operator, because the algorithm branches on the presence of the [[BoundTargetFunction]] internal slot. As a result, qux instanceof Proxied will throw instead of returning a boolean.

Having scanned the uses in the spec of the [[Bound*]] internal slots as well as the expression “bound function”, I think that only those two cases (new and instanceof) are problematic.

@allenwb

This comment has been minimized.

Show comment
Hide comment
@allenwb

allenwb Dec 21, 2017

Member

isArray drilling throw proxies when the target object is an Array exotic object was a late addition to ES6 and one that I opposed.

The argument in favor was that people would just expect it to work that way. The reason I opposed it was exactly the reasons being discussed in this thread. ES proxies, in fact, are not transparent forwarders and there are many ways to trip over this lack of transparency. It was easy enough to get Array.isArray to drill through as a special case. But, as we see here, that just creates the expectation that other non-proxy transparent characteristics of various objects should also be special cased to create the illusion of transparency. But there are too many of them. (for example class private fields will have the similar proxying issues as internal slots).

People who expect

new Proxy(O, {}) to behave exactly like O.

don't understand ES proxies. If they are going to use then, they need to correct that deficiency. Adding special cases that make some Proxies appear to be transparent simply adds to the confusion.

Member

allenwb commented Dec 21, 2017

isArray drilling throw proxies when the target object is an Array exotic object was a late addition to ES6 and one that I opposed.

The argument in favor was that people would just expect it to work that way. The reason I opposed it was exactly the reasons being discussed in this thread. ES proxies, in fact, are not transparent forwarders and there are many ways to trip over this lack of transparency. It was easy enough to get Array.isArray to drill through as a special case. But, as we see here, that just creates the expectation that other non-proxy transparent characteristics of various objects should also be special cased to create the illusion of transparency. But there are too many of them. (for example class private fields will have the similar proxying issues as internal slots).

People who expect

new Proxy(O, {}) to behave exactly like O.

don't understand ES proxies. If they are going to use then, they need to correct that deficiency. Adding special cases that make some Proxies appear to be transparent simply adds to the confusion.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Dec 21, 2017

Member

@allenwb given that, is there any reason not to have something equivalent to Proxy.isProxy?

Member

ljharb commented Dec 21, 2017

@allenwb given that, is there any reason not to have something equivalent to Proxy.isProxy?

@claudepache

This comment has been minimized.

Show comment
Hide comment
@claudepache

claudepache Dec 21, 2017

Contributor

@ljharb Proxy.isProxy (like Function.isGenerator) gives information about how the object is implemented, not how it behaves. It is often the answer to the wrong question.

The issue of non-transparency just means that implementing correctly an object with the help of Proxy requires more work than new Proxy(O, { /* empty */ }).

Contributor

claudepache commented Dec 21, 2017

@ljharb Proxy.isProxy (like Function.isGenerator) gives information about how the object is implemented, not how it behaves. It is often the answer to the wrong question.

The issue of non-transparency just means that implementing correctly an object with the help of Proxy requires more work than new Proxy(O, { /* empty */ }).

@LucaFranceschini

This comment has been minimized.

Show comment
Hide comment
@LucaFranceschini

LucaFranceschini Dec 21, 2017

isArray drilling throw proxies when the target object is an Array exotic object was a late addition to ES6 and one that I opposed.

The argument in favor was that people would just expect it to work that way. The reason I opposed it was exactly the reasons being discussed in this thread. ES proxies, in fact, are not transparent forwarders and there are many ways to trip over this lack of transparency. It was easy enough to get Array.isArray to drill through as a special case. But, as we see here, that just creates the expectation that other non-proxy transparent characteristics of various objects should also be special cased to create the illusion of transparency. But there are too many of them. (for example class private fields will have the similar proxying issues as internal slots).

People who expect

new Proxy(O, {}) to behave exactly like O.

don't understand ES proxies. If they are going to use then, they need to correct that deficiency. Adding special cases that make some Proxies appear to be transparent simply adds to the confusion.

Ok, ES proxies just don't work like that, point taken. However, generally speaking, proxies are expected to be as much behaviorally equivalent as possible to the wrapped object (expect for identity, of course). That's how the proxy pattern is known, so it must not come as a surprise if ES proxies generate confusion. What I'm saying is that I can take an apple, call it a banana and tell everyone expecting it to be a banana they're wrong. Of course I would be technically right, but still, naming matters...

</noob rant>

Coming back to comment 0: would it be sensible to set newTarget to the target function object if it was the proxy itself, before going on with construction, just like bound functions do? Furthermore, in the example I made, not only the result may be unexpected but also misleading, since you can access a prototype property through the proxy, but doing so will give the bound function property and not the original function's one.

LucaFranceschini commented Dec 21, 2017

isArray drilling throw proxies when the target object is an Array exotic object was a late addition to ES6 and one that I opposed.

The argument in favor was that people would just expect it to work that way. The reason I opposed it was exactly the reasons being discussed in this thread. ES proxies, in fact, are not transparent forwarders and there are many ways to trip over this lack of transparency. It was easy enough to get Array.isArray to drill through as a special case. But, as we see here, that just creates the expectation that other non-proxy transparent characteristics of various objects should also be special cased to create the illusion of transparency. But there are too many of them. (for example class private fields will have the similar proxying issues as internal slots).

People who expect

new Proxy(O, {}) to behave exactly like O.

don't understand ES proxies. If they are going to use then, they need to correct that deficiency. Adding special cases that make some Proxies appear to be transparent simply adds to the confusion.

Ok, ES proxies just don't work like that, point taken. However, generally speaking, proxies are expected to be as much behaviorally equivalent as possible to the wrapped object (expect for identity, of course). That's how the proxy pattern is known, so it must not come as a surprise if ES proxies generate confusion. What I'm saying is that I can take an apple, call it a banana and tell everyone expecting it to be a banana they're wrong. Of course I would be technically right, but still, naming matters...

</noob rant>

Coming back to comment 0: would it be sensible to set newTarget to the target function object if it was the proxy itself, before going on with construction, just like bound functions do? Furthermore, in the example I made, not only the result may be unexpected but also misleading, since you can access a prototype property through the proxy, but doing so will give the bound function property and not the original function's one.

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Dec 21, 2017

@claudepache

The issue of non-transparency just means that implementing correctly an object with the help of Proxy requires more work than new Proxy(O, { /* empty */ }).

I think it's fundamentally impossible to make a proxy completely transparent for a target with internal slots. In p = new Proxy(t, { /* elaborate sophisticated something */ }), you either get p.method() to work or get p.method === t.method (assuming you do not want to harm t or its prototype).

To solve this problem, we would need every single slot lookup to (recursively?) unwrap proxies. I agree with @allenwb that this is not reasonable.

In general, when wrapping instances of builtin types in a proxy, one actually needs to intercept all the method calls that mutate the internal slots of the object. Subclassing is by far better suited for that purpose anyway, and if necessary one can still inject the proxy in the prototype chain.

bergus commented Dec 21, 2017

@claudepache

The issue of non-transparency just means that implementing correctly an object with the help of Proxy requires more work than new Proxy(O, { /* empty */ }).

I think it's fundamentally impossible to make a proxy completely transparent for a target with internal slots. In p = new Proxy(t, { /* elaborate sophisticated something */ }), you either get p.method() to work or get p.method === t.method (assuming you do not want to harm t or its prototype).

To solve this problem, we would need every single slot lookup to (recursively?) unwrap proxies. I agree with @allenwb that this is not reasonable.

In general, when wrapping instances of builtin types in a proxy, one actually needs to intercept all the method calls that mutate the internal slots of the object. Subclassing is by far better suited for that purpose anyway, and if necessary one can still inject the proxy in the prototype chain.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Dec 21, 2017

Member

@claudepache right; i'm not saying it's a useful thing to know, i'm asking if there's any technical reason why we couldn't expose that functionality. The existence of an "is proxy" function would certainly, imo, mitigate confusion around proxy transparency (namely, that proxies can only be transparent when all of the builtins can also be proxied).

Member

ljharb commented Dec 21, 2017

@claudepache right; i'm not saying it's a useful thing to know, i'm asking if there's any technical reason why we couldn't expose that functionality. The existence of an "is proxy" function would certainly, imo, mitigate confusion around proxy transparency (namely, that proxies can only be transparent when all of the builtins can also be proxied).

@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Dec 21, 2017

Contributor

cc @erights

I believe an ordinary object can be transparently proxied currently, at least if its prototype chain is locked down. It's only functions and exotics which have this issue.

Contributor

bakkot commented Dec 21, 2017

cc @erights

I believe an ordinary object can be transparently proxied currently, at least if its prototype chain is locked down. It's only functions and exotics which have this issue.

@Perelandric

This comment has been minimized.

Show comment
Hide comment
@Perelandric

Perelandric Dec 21, 2017

A Proxy instance is not a function, but it makes us believe it is via typeof. So what function is it? I think the most reasonable answer is that it is the function it wraps, even if not by identity.


Also consider closures. It seems inconsistent that a proxied bound function uses the same closure of the original function but doesn't use its prototype.

const F = (function(n) {
  return function() { this.foo = ++n };
})(0);
const BF = F.bind({});
const PBF = new Proxy(BF, {});

console.log(new F().foo); // 1
console.log(new BF().foo); // 2
console.log(new PBF().foo); // 3

console.log(new F() instanceof F); // true
console.log(new BF() instanceof F); // true
console.log(new PBF() instanceof F); // false

So a proxied bound function is related to the original's closure, but unrelated to its .prototype object.

Perelandric commented Dec 21, 2017

A Proxy instance is not a function, but it makes us believe it is via typeof. So what function is it? I think the most reasonable answer is that it is the function it wraps, even if not by identity.


Also consider closures. It seems inconsistent that a proxied bound function uses the same closure of the original function but doesn't use its prototype.

const F = (function(n) {
  return function() { this.foo = ++n };
})(0);
const BF = F.bind({});
const PBF = new Proxy(BF, {});

console.log(new F().foo); // 1
console.log(new BF().foo); // 2
console.log(new PBF().foo); // 3

console.log(new F() instanceof F); // true
console.log(new BF() instanceof F); // true
console.log(new PBF() instanceof F); // false

So a proxied bound function is related to the original's closure, but unrelated to its .prototype object.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Dec 21, 2017

Member

@Perelandric a proxy instance of a function has a [[Call]] internal method, which is what typeof checks, and what makes it a function.

Member

ljharb commented Dec 21, 2017

@Perelandric a proxy instance of a function has a [[Call]] internal method, which is what typeof checks, and what makes it a function.

@erights

This comment has been minimized.

Show comment
Hide comment
@erights

erights Dec 21, 2017

I do not have time to read this thread right now, so apologies if this has already been covered.

The goal of proxies has never been that an individual proxy be transparent with high fidelity. It is that membranes be transparent with high fidelity. Class private state do not threaten that in the slightest. Internal properties, under normal use patterns, do not threaten that either. The relevant difference between private state and internal properties is only that the former is per realm whereas the latter is cross-realm. If there were no cross-realm visibility of internal properties to builtin operations, then they would be as unproblematic as class private state.

If I have a dry function proxy p to a wet function g, and a dry function proxy F to a wet Function and I do

F.prototype.toString.call(p)

everything works fine. The only reason that the present issue is a genuine problem is that Function.prototype.toString works on functions from other realms.

erights commented Dec 21, 2017

I do not have time to read this thread right now, so apologies if this has already been covered.

The goal of proxies has never been that an individual proxy be transparent with high fidelity. It is that membranes be transparent with high fidelity. Class private state do not threaten that in the slightest. Internal properties, under normal use patterns, do not threaten that either. The relevant difference between private state and internal properties is only that the former is per realm whereas the latter is cross-realm. If there were no cross-realm visibility of internal properties to builtin operations, then they would be as unproblematic as class private state.

If I have a dry function proxy p to a wet function g, and a dry function proxy F to a wet Function and I do

F.prototype.toString.call(p)

everything works fine. The only reason that the present issue is a genuine problem is that Function.prototype.toString works on functions from other realms.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Dec 21, 2017

Member

@erights would it be worth making Function.prototype.toString unwrap proxies, which would make proxies to functions indistinguishable (like arrays)?

Member

ljharb commented Dec 21, 2017

@erights would it be worth making Function.prototype.toString unwrap proxies, which would make proxies to functions indistinguishable (like arrays)?

@Perelandric

This comment has been minimized.

Show comment
Hide comment
@Perelandric

Perelandric Dec 21, 2017

@ljharb That is the mechanics of typeof, and is what makes typeof return the string "function". But that's just another way of saying that the decision was made to lead us to believe that it is a function. Certainly it only has [[Call]] by virtue of the proxied function, which brings us back to the original question.

I think it would be hard to deny the inconsistency of there sometimes being an implied relationship to a specific function object, and sometimes not.

Perelandric commented Dec 21, 2017

@ljharb That is the mechanics of typeof, and is what makes typeof return the string "function". But that's just another way of saying that the decision was made to lead us to believe that it is a function. Certainly it only has [[Call]] by virtue of the proxied function, which brings us back to the original question.

I think it would be hard to deny the inconsistency of there sometimes being an implied relationship to a specific function object, and sometimes not.

@erights

This comment has been minimized.

Show comment
Hide comment
@erights

erights Dec 21, 2017

@ljharb Perhaps. First, I apologize for having missed this issue --- I should have seen this one coming well before it became an issue in practice.

The reason why it would be ok in this case is that the internal slot in question holds only the source code string which would be rendered, and so communicates only data that is available to anyone with a direct reference to the underlying function anyway.

The reason it might still not be ok is that the source code of the underlying function does not necessarily represent the [[Call]] behavior of the proxy, even though the proxy's [[Call]] behavior includes calling that function's [[Call]]. This counter-argument is worth discussing.

The reason it might be ok is that the [[Call]] behavior of the target represents the likely behavior of the proxy, when the proxy is part of a membrane trying to be transparent. This stance would demote the security role of Function.prototype.toString to presenting the source code the function alleges represents its [[Call]] behavior.

If we decide against, Function.prototype.toString.call(functionProxy) should return a string that conforms to the spec for how builtin functions print, i.e.,

function foo() { [native code] }

A function proxy is a callable and should definitely do one or the other. Of these two options, I don't yet know which I favor.

erights commented Dec 21, 2017

@ljharb Perhaps. First, I apologize for having missed this issue --- I should have seen this one coming well before it became an issue in practice.

The reason why it would be ok in this case is that the internal slot in question holds only the source code string which would be rendered, and so communicates only data that is available to anyone with a direct reference to the underlying function anyway.

The reason it might still not be ok is that the source code of the underlying function does not necessarily represent the [[Call]] behavior of the proxy, even though the proxy's [[Call]] behavior includes calling that function's [[Call]]. This counter-argument is worth discussing.

The reason it might be ok is that the [[Call]] behavior of the target represents the likely behavior of the proxy, when the proxy is part of a membrane trying to be transparent. This stance would demote the security role of Function.prototype.toString to presenting the source code the function alleges represents its [[Call]] behavior.

If we decide against, Function.prototype.toString.call(functionProxy) should return a string that conforms to the spec for how builtin functions print, i.e.,

function foo() { [native code] }

A function proxy is a callable and should definitely do one or the other. Of these two options, I don't yet know which I favor.

@allenwb

This comment has been minimized.

Show comment
Hide comment
@allenwb

allenwb Dec 21, 2017

Member

Note that one of the reason that Array.isArray is problematic is that is pierces the encapsulation barrier of the internal implementation of objects. Specifically it knows how, at the implementation level, to identify an "array exotic object" and a "proxy exotic object".

One of the design use cases for Proxy was to enable self-hosting of built-ins that are exotic. An implementation that actually used Proxy to self-host built-in Array instances would have to have some sort of implementation dependent way to identify Array instances and its implementation of Array.isArray would have to use that identification mechanism in its implementation of step 2 of IsArray.

Member

allenwb commented Dec 21, 2017

Note that one of the reason that Array.isArray is problematic is that is pierces the encapsulation barrier of the internal implementation of objects. Specifically it knows how, at the implementation level, to identify an "array exotic object" and a "proxy exotic object".

One of the design use cases for Proxy was to enable self-hosting of built-ins that are exotic. An implementation that actually used Proxy to self-host built-in Array instances would have to have some sort of implementation dependent way to identify Array instances and its implementation of Array.isArray would have to use that identification mechanism in its implementation of step 2 of IsArray.

@erights

This comment has been minimized.

Show comment
Hide comment
@erights

erights Dec 21, 2017

I favor the full pass-through. There is no realistic scenario where Function.prototype.toString.call provides any stronger guarantee than representing the [[Call]] behavior that the function alleges itself to have. Since there is no forced veracity guarantee anyway, we may as well go all the way.

Attn: @tvcutsem @ajvincent

erights commented Dec 21, 2017

I favor the full pass-through. There is no realistic scenario where Function.prototype.toString.call provides any stronger guarantee than representing the [[Call]] behavior that the function alleges itself to have. Since there is no forced veracity guarantee anyway, we may as well go all the way.

Attn: @tvcutsem @ajvincent

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Dec 23, 2017

Member

@bakkot are you calling everything with internal slots exotic?

Member

littledan commented Dec 23, 2017

@bakkot are you calling everything with internal slots exotic?

@erights

This comment has been minimized.

Show comment
Hide comment
@erights

erights Dec 23, 2017

@littledan yes, everything with internal slots, beyond the universal ones, is exotic.

@allenwb @bterlson do I have that right?

erights commented Dec 23, 2017

@littledan yes, everything with internal slots, beyond the universal ones, is exotic.

@allenwb @bterlson do I have that right?

@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Dec 23, 2017

Contributor

@littledan Sorry, wrong term; I always forget that "exotic object" means something specific in the spec, not just "weird object". I mean roughly "exotic objects + objects with extra slots", I guess.

@erights: since I just looked it up myself: the spec defines "exotic object" to be an object which has non-default behavior for at least one of the standard internal methods (basically the things a proxy can intercept). This is not quite equivalent to "has an extra internal slot"; for example Maps have their own internal slots but are not "exotic objects" (I think).

Contributor

bakkot commented Dec 23, 2017

@littledan Sorry, wrong term; I always forget that "exotic object" means something specific in the spec, not just "weird object". I mean roughly "exotic objects + objects with extra slots", I guess.

@erights: since I just looked it up myself: the spec defines "exotic object" to be an object which has non-default behavior for at least one of the standard internal methods (basically the things a proxy can intercept). This is not quite equivalent to "has an extra internal slot"; for example Maps have their own internal slots but are not "exotic objects" (I think).

@evilpie

This comment has been minimized.

Show comment
Hide comment
@evilpie

evilpie Dec 23, 2017

Contributor

In Firefox we used to support native functions called on proxied objects. We still have support for this to make cross-compartment calls and other wrappers work. https://searchfox.org/mozilla-central/rev/78bc55ae1f1909be5ffc66c0ec447accc639edd3/js/public/CallNonGenericMethod.h#31

Oh, I should not that this is quite tricky to do correctly. We aren't completely ready to handle this. See bug 1111243

Contributor

evilpie commented Dec 23, 2017

In Firefox we used to support native functions called on proxied objects. We still have support for this to make cross-compartment calls and other wrappers work. https://searchfox.org/mozilla-central/rev/78bc55ae1f1909be5ffc66c0ec447accc639edd3/js/public/CallNonGenericMethod.h#31

Oh, I should not that this is quite tricky to do correctly. We aren't completely ready to handle this. See bug 1111243

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Dec 28, 2017

Member

@erights I agree with @bakkot fwiw, an ordinary object can have any number of slots as long as it has the default behavior for the essential internal methods.

Member

bterlson commented Dec 28, 2017

@erights I agree with @bakkot fwiw, an ordinary object can have any number of slots as long as it has the default behavior for the essential internal methods.

@LucaFranceschini

This comment has been minimized.

Show comment
Hide comment
@LucaFranceschini

LucaFranceschini Jan 31, 2018

FWIW, a possible work-around for the unexpected behavior I described in the OP is to manually fix new.target to the original function, if it appears to be the proxy. But there may be corner cases I'm not taking into account.

function Foo () { }
Foo.prototype.bar = 'baz'
const Bound = Foo.bind(42)
new Bound().bar  // 'baz'

const handler = { }
const Proxied = new Proxy(Bound, handler)
handler.construct = (target, args, newTarget) =>
    Reflect.construct(target, args, newTarget === Proxied ? target : newTarget)
new Proxied().bar  // 'baz'

Caveat: the handler need a reference to the proxy, which in turns needs an existing handler to be created.

toString on proxies is still a problem though.

LucaFranceschini commented Jan 31, 2018

FWIW, a possible work-around for the unexpected behavior I described in the OP is to manually fix new.target to the original function, if it appears to be the proxy. But there may be corner cases I'm not taking into account.

function Foo () { }
Foo.prototype.bar = 'baz'
const Bound = Foo.bind(42)
new Bound().bar  // 'baz'

const handler = { }
const Proxied = new Proxy(Bound, handler)
handler.construct = (target, args, newTarget) =>
    Reflect.construct(target, args, newTarget === Proxied ? target : newTarget)
new Proxied().bar  // 'baz'

Caveat: the handler need a reference to the proxy, which in turns needs an existing handler to be created.

toString on proxies is still a problem though.

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