You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Jan 25, 2022. It is now read-only.
Repository navigation
This repository was archived by the owner on Jan 25, 2022. It is now read-only.
Branding guarantees do not match web platform classes, due to return override #179
@ljharb it's not; it's only used to illustrate a "failure" of the brand checks in the example.
@jridgewell good catch, I think you are right. I've lost track of the current proposal in that regard: can we still use private fields with constructors that never call super()?
@ljharb I don't understand; the two things are disconnected.
@jridgewell thanks. That's not great; it drives home the point that we could not use private fields for self-hosting any classes involved in inheritance hierarchies. I guess back to weak maps for those, unless this proposal's champions have any possible fixes?
@domenic i guess i'm not understanding why you'd override __proto__ to self-host in an inheritance chain rather than using class extends and super. (if this is off topic we can certainly discuss separately)
The behavior you describe looks to me like the branding is on the base class, rather than the subclass. For example, from the behavior you describe, I'd expect Text and Node to look something like
classNode{
#isNode;staticassertNode(o){o.#isNode;}}constassertNode=Node.assertNode;deleteNode.assertNode;// yes, I know this is awkward; decorators can make it a bit simpler, but it's just for illustrationclassTextextendsNode{splitText(offset){assertNode(this);// remaining implementation}}
Of course, this doesn't match with the observed behavior of not actually invoking the superclass.
I guess back to weak maps for those, unless this proposal's champions have any possible fixes?
Two possible alternatives:
make the web platform classes actually invoke their superclass, like a regular class; I imagine implementations would very quickly add a fast path to do what they currently do as long as the superclass is the expected thing
Edit: sorry, I misunderstood. The following works for web platform classes:
consto={};Text.__proto__=function(){returno;};Text.prototype.splitText.call(newText)// does not throw
and not for the implementation below. So, yes, I can't see a way of making this work without a WeakMap or changes to how web platform classes work.
Original incorrect idea follows:
On further thought, a third alternative would be to do something like:
classTextextendsNode{
#privateStuff ='abc';constructor(){if(Text.__proto__!==Node){return{__proto__: Text.__proto__};}super();// installs #privateStuff on `this`, which is guaranteed to be a Node}splitText(arg){returnthis.#privateStuff.split(arg);}}
I think this matches web platform classes in all particulars, at least as described in this thread, down to not invoking artificial (and therefore observable) superclasses:
(newText).splitText('b');// worksconsto={};Text.__proto__=function(){console.log('reached');returno;};newText();// does not print 'reached', is not === oText.prototype.splitText.call(o);// throws
@nicolo-ribaudo: Don't weak maps/sets have the same problem?
Yes, it's a core issue with subclassing.
@ljharb: i guess i'm not understanding why you'd override proto to self-host in an inheritance chain rather than using class extends and super.
He's not. He want's to implement class Text extends Node and Uint8Array natively. But with native class syntax, there's the ability to override the superclass after declaration. These builtin classes do not allow this, meaning there's a difference.
Further, if we were to fix the override difference, there's still the issue of allowing a subclasses privates to be installed on a foreign instance by overriding the superclass.
@bakkot: For example, from the behavior you describe, I'd expect Text and Node to look something like... assertNode(this)
Doesn't this defeat the point of branding by default if I still have to manually brand?
@domenic: Do these classes actually use inheritance, other than to define the prototype's inheritance? If they don't, they could be implemented as base classes, then override the prototypes.
To clarify what I meant about "back to weak maps", I think the following would be the only way to implement web platform / ES262 class semantics:
const_data=newWeakMap();classTextextendsNode{// let's ignore CharacterData for this exampleconstructor(data=""){consto=Object.create(new.target.prototype);_data.set(o,data);returno;}splitText(offset){if(!_data.has(this)){thrownewTypeError("Not a Text!");}return_data.get(this).substring(offset);// (not really what splitText does)}}
i.e. it's a shame there's no way to use private fields with the return-override feature that apparently is always in use by web platform and ES262 classes. (Does ES402 have any subclasses?)
@bakkot regarding your (1) and (2), do you think it would be reasonable to do either of those two alternatives to all the class hierarchies in the ES262 spec which also have this problem? I think that's mostly the typed arrays, although maybe also the various Function subclasses.
I personally wouldn't be opposed to changing either 1 or 2 for classes in the spec, but would prefer other options. Spec classes can be weird in other ways anyway.
Per @jridgewell's comment, could you do something like
? i.e. manually wire up the class hierarchy, but keep Text as nominally a "base" class, i.e., such that it does not invoke Node in the process of constructing its instances. This gives you all of
the correct inheritance for the class objects themselves
the correct inheritance for instances
Text has private fields
constructing Text does not invoke Text.__proto__
if you modify the Text.__proto__, instances of Text inherit from it, but still have the private fields of Text.
This only works as long as Node does not have its own private fields it needs to install on instances of Text. (Same for your example with Object.create, of course.)
It's a shame we couldn't do something similar to
You can seriously abuse the return-override trick to install private fields on arbitrary objects if you really want to:
Do you think they should be weird in these ways, though? I guess I'm unclear why spec classes (in particular ES262 classes) don't follow the same patterns as author-defined ones.
Per @jridgewell's comment, could you do something like
Hmm yeah, that seems superior to my Object.create() approach at least....
This only works as long as Node does not have its own private fields it needs to install on instances of Text.
Right, they kind of do... i.e. these things should be branded as Nodes, not just as Texts.
But I guess this gets into a whole thing where the real requirements are much more complicated than my simple examples so far, e.g. you want "protected" type behavior so the base class can declare private fields that Text's methods can access. And the architecture for doing that properly would probably instead involve a whole different setup (the impl/wrapper distinction).
So not being able to meet the real requirements is OK-ish, I guess. (Which makes me wonder why I opened this thread in the first place...)
I'm not sure there's much actionable left for this thread; thanks for all the attention to detail on walking me through it. I guess it might still be worth pursuing the question as to whether ES262 classes (and probably web classes, following them) should call into their super-constructor.
Right, they kind of do... i.e. these things should be branded as Nodes, not just as Texts.
Then my approach won't work. You have to construct a Node instance to install its private fields.
Or, use decorators to extract the necessary private field's key from Node, and another decorator to install that key onto Text. That would let you keep the same "Text is a base class" hack.
This would be simpler if you could install private fields onto foreign objects. It's easy to achieve the same guarantees as branding on top of general encapsulation, but rather cumbersome to do normal property semantics the other way.
@devsnek I don't quite follow. Internal slots are not tied to things that have a certain prototype chain, but rather to what the constructor outputs. Public fields work like this too--they're just a way that the constructor can add things to the object, and don't have anything to do with prototype chains.
The issue here was, even if you mess up the prototype chain of the constructor, you should be able to have the private fields added to the instance. I agree that it's OK to be able to write a class that doesn't have the private fields and instead returns something else. The question was, how should you be able to use the original prototype chain and still add the private fields/methods to the instance, when you want to?
@Igmat I can see how making private symbols that you can add to arbitrary existing instances is a solution to the problem, but does @jridgewell 's proposal (which doesn't do this, at least in the first step) address it?
@littledan@devsnek
I don't know if I'm getting the right impression, but let me ask a question. Since it's already the case that the functions within the lexical scope of the class definition contain the only code that is aware of and able to access private data defined in that class, that all instances of that class contain private data accessible via the same name (the type of that name is irrelevant for this question), and that the only guarantee of brand-checking is that a given object has passed through a given constructor, for the sake of attaching private fields, is it a priority that the object in question is the result of using new on that class?
Or put more concisely, what does it matter where the object came from? Is the constructor of a class that has private fields free to put those fields on any object?
is it a priority that the object in question is the result of using new on that class?
In this issue, we're talking about matching the branding semantics of web platform and JS builtin classes, which do something special with things that were the output of new.
I'm going to unsubscribe from my own thread as this appears to have degenerated into being the same as every other issue thread in this repository, i.e. a place for people to complain about the current proposal and push their counterproposals.
Let me know through out-of-band communication if any more interesting on-topic discussion requires my attention.
In this issue, we're talking about matching the branding semantics of web platform and JS builtin classes, which do something special with things that were the output of new.
That's a prime example of how existing "weird" (exotic) behaviors are cutting into what a good language feature can actually be, at the expense of end users.
New features don't have to match what existing weird/exotic objects do. Give users the best features for their code, and leave the old mistakes as they are without limiting innovation just to explain existing mistakes.
rant
a place for people to complain about the current proposal and push their counterproposals.
Also a place for people who aren't paid while writing here to feel as if there's little hope. :)
Consider yourself very lucky if you can write here while "at work" and getting paid!
More importantly than creating other proposals, is stopping or slowing this one.
Requiring that some other proposal be accepted in order to stop this one (or else this one will get released and implemented) is not a nice thing for the community to be subjected to.
"Make a better proposal now or else this one will be implemented with its flaws and all."
Its in some ways similar to "Jane gets the electric chair in one week, for killing John, unless you can prove she didn't do it" while having seen someone else do it with your own eyes but not fortunate enough to have evidence.
Activity
ljharb commentedon Dec 10, 2018
Can you elaborate on why the return override technique is needed to self-host classes involved in inheritance hierarchies?
jridgewell commentedon Dec 10, 2018
Is this because these classes don't respect overriding the super class?
domenic commentedon Dec 10, 2018
@ljharb it's not; it's only used to illustrate a "failure" of the brand checks in the example.
@jridgewell good catch, I think you are right. I've lost track of the current proposal in that regard: can we still use private fields with constructors that never call
super()?ljharb commentedon Dec 10, 2018
@domenic then why does it mean this?
I believe that in a subclass, calling
superis a requirement to install any private fields.jridgewell commentedon Dec 10, 2018
Under normal circumstances, no. Fields are installed in a weird step after the parent constructor returns, but before the
superreturns.If these classes don't actually have a comparable
super()call, then I don't know where they'd be installed.domenic commentedon Dec 10, 2018
@ljharb I don't understand; the two things are disconnected.
@jridgewell thanks. That's not great; it drives home the point that we could not use private fields for self-hosting any classes involved in inheritance hierarchies. I guess back to weak maps for those, unless this proposal's champions have any possible fixes?
nicolo-ribaudo commentedon Dec 10, 2018
Don't weak maps/sets have the same problem?
ljharb commentedon Dec 10, 2018
@domenic i guess i'm not understanding why you'd override
__proto__to self-host in an inheritance chain rather than usingclass extendsandsuper. (if this is off topic we can certainly discuss separately)bakkot commentedon Dec 10, 2018
@domenic:
The behavior you describe looks to me like the branding is on the base class, rather than the subclass. For example, from the behavior you describe, I'd expect
TextandNodeto look something likeThis matches the behavior you observe:
Of course, this doesn't match with the observed behavior of not actually invoking the superclass.
Two possible alternatives:
bakkot commentedon Dec 10, 2018
Edit: sorry, I misunderstood. The following works for web platform classes:
and not for the implementation below. So, yes, I can't see a way of making this work without a
WeakMapor changes to how web platform classes work.Original incorrect idea follows:
On further thought, a third alternative would be to do something like:
I think this matches web platform classes in all particulars, at least as described in this thread, down to not invoking artificial (and therefore observable) superclasses:
jridgewell commentedon Dec 10, 2018
Yes, it's a core issue with subclassing.
He's not. He want's to implement
class Text extends NodeandUint8Arraynatively. But with native class syntax, there's the ability to override the superclass after declaration. These builtin classes do not allow this, meaning there's a difference.Further, if we were to fix the override difference, there's still the issue of allowing a subclasses privates to be installed on a foreign instance by overriding the superclass.
Doesn't this defeat the point of branding by default if I still have to manually brand?
@domenic: Do these classes actually use inheritance, other than to define the prototype's inheritance? If they don't, they could be implemented as base classes, then override the prototypes.
domenic commentedon Dec 10, 2018
To clarify what I meant about "back to weak maps", I think the following would be the only way to implement web platform / ES262 class semantics:
It's a shame we couldn't do something similar to
i.e. it's a shame there's no way to use private fields with the return-override feature that apparently is always in use by web platform and ES262 classes. (Does ES402 have any subclasses?)
@bakkot regarding your (1) and (2), do you think it would be reasonable to do either of those two alternatives to all the class hierarchies in the ES262 spec which also have this problem? I think that's mostly the typed arrays, although maybe also the various
Functionsubclasses.bakkot commentedon Dec 11, 2018
I personally wouldn't be opposed to changing either 1 or 2 for classes in the spec, but would prefer other options. Spec classes can be weird in other ways anyway.
Per @jridgewell's comment, could you do something like
? i.e. manually wire up the class hierarchy, but keep
Textas nominally a "base" class, i.e., such that it does not invokeNodein the process of constructing its instances. This gives you all ofTexthas private fieldsTextdoes not invokeText.__proto__Text.__proto__, instances ofTextinherit from it, but still have the private fields ofText.This only works as long as
Nodedoes not have its own private fields it needs to install on instances ofText. (Same for your example withObject.create, of course.)You can seriously abuse the return-override trick to install private fields on arbitrary objects if you really want to:
buuuut don't.
domenic commentedon Dec 11, 2018
Do you think they should be weird in these ways, though? I guess I'm unclear why spec classes (in particular ES262 classes) don't follow the same patterns as author-defined ones.
Hmm yeah, that seems superior to my Object.create() approach at least....
Right, they kind of do... i.e. these things should be branded as
Nodes, not just asTexts.But I guess this gets into a whole thing where the real requirements are much more complicated than my simple examples so far, e.g. you want "protected" type behavior so the base class can declare private fields that
Text's methods can access. And the architecture for doing that properly would probably instead involve a whole different setup (the impl/wrapper distinction).So not being able to meet the real requirements is OK-ish, I guess. (Which makes me wonder why I opened this thread in the first place...)
I'm not sure there's much actionable left for this thread; thanks for all the attention to detail on walking me through it. I guess it might still be worth pursuing the question as to whether ES262 classes (and probably web classes, following them) should call into their super-constructor.
jridgewell commentedon Dec 11, 2018
Then my approach won't work. You have to construct a
Nodeinstance to install its private fields.Or, use decorators to extract the necessary private field's key from
Node, and another decorator to install that key ontoText. That would let you keep the same "Textis a base class" hack.This would be simpler if you could install private fields onto foreign objects. It's easy to achieve the same guarantees as branding on top of general encapsulation, but rather cumbersome to do normal property semantics the other way.
38 remaining items
littledan commentedon Jan 8, 2019
@devsnek I don't quite follow. Internal slots are not tied to things that have a certain prototype chain, but rather to what the constructor outputs. Public fields work like this too--they're just a way that the constructor can add things to the object, and don't have anything to do with prototype chains.
The issue here was, even if you mess up the prototype chain of the constructor, you should be able to have the private fields added to the instance. I agree that it's OK to be able to write a class that doesn't have the private fields and instead returns something else. The question was, how should you be able to use the original prototype chain and still add the private fields/methods to the instance, when you want to?
@Igmat I can see how making private symbols that you can add to arbitrary existing instances is a solution to the problem, but does @jridgewell 's proposal (which doesn't do this, at least in the first step) address it?
rdking commentedon Jan 8, 2019
@littledan @devsnek
I don't know if I'm getting the right impression, but let me ask a question. Since it's already the case that the functions within the lexical scope of the class definition contain the only code that is aware of and able to access private data defined in that class, that all instances of that class contain private data accessible via the same name (the type of that name is irrelevant for this question), and that the only guarantee of brand-checking is that a given object has passed through a given constructor, for the sake of attaching private fields, is it a priority that the object in question is the result of using
newon that class?Or put more concisely, what does it matter where the object came from? Is the constructor of a class that has private fields free to put those fields on any object?
littledan commentedon Jan 8, 2019
In this issue, we're talking about matching the branding semantics of web platform and JS builtin classes, which do something special with things that were the output of
new.Igmat commentedon Jan 8, 2019
littledan commentedon Jan 8, 2019
littledan commentedon Jan 8, 2019
Igmat commentedon Jan 8, 2019
littledan commentedon Jan 8, 2019
jridgewell commentedon Jan 8, 2019
rdking commentedon Jan 8, 2019
domenic commentedon Jan 8, 2019
I'm going to unsubscribe from my own thread as this appears to have degenerated into being the same as every other issue thread in this repository, i.e. a place for people to complain about the current proposal and push their counterproposals.
Let me know through out-of-band communication if any more interesting on-topic discussion requires my attention.
littledan commentedon Jan 8, 2019
I've hidden comments that were about other proposals. Folks can follow up about private symbols in https://github.com/zenparsing/proposal-private-symbols .
trusktr commentedon Apr 7, 2019
That's a prime example of how existing "weird" (exotic) behaviors are cutting into what a good language feature can actually be, at the expense of end users.
New features don't have to match what existing weird/exotic objects do. Give users the best features for their code, and leave the old mistakes as they are without limiting innovation just to explain existing mistakes.
rant
Also a place for people who aren't paid while writing here to feel as if there's little hope. :)
Consider yourself very lucky if you can write here while "at work" and getting paid!
More importantly than creating other proposals, is stopping or slowing this one.
Requiring that some other proposal be accepted in order to stop this one (or else this one will get released and implemented) is not a nice thing for the community to be subjected to.
"Make a better proposal now or else this one will be implemented with its flaws and all."
Its in some ways similar to "Jane gets the electric chair in one week, for killing John, unless you can prove she didn't do it" while having seen someone else do it with your own eyes but not fortunate enough to have evidence.
littledan commentedon Apr 8, 2019
I think we have examined this topic sufficiently, so I'm closing the thread.