02:54 | <bendtherules> | @ljharb: Thanks for the heads up. Github app notifications are flaky at times. |
03:02 | <bendtherules> | bradleymeck: Trying to understand the conversation. So what is the potential problem with object[key] with user-provided key? |
03:02 | <bendtherules> | Accessing __proto__ in the key? |
03:03 | <bendtherules> | Even then you can only set proto, not change properties within it. |
03:15 | <devsnek> | littledan: what is finalizationRegistry.[[Realm]] for |
12:22 | <bendtherules> | ljharb: Done |
12:23 | <bendtherules> | npm watch script seems broken? https://github.com/tc39/ecma262/issues/2083 |
15:04 | <devsnek> | v8's test262 runner goes past 100% coverage |
15:04 | <devsnek> | i don't like this |
16:25 | <ljharb> | bendtherules: thanks |
17:17 | <bradleymeck> | bendtherules: see https://twitter.com/bradleymeck/status/1280209566907670530 , basically you have this whole security firm audit noise from people accessing things on prototypes with dynamic access |
17:18 | <bradleymeck> | and it is a lot of noise if you use `npm audit` or github vulnerability checks |
17:32 | <ljharb> | (both of which are on by default) |
17:33 | <ljharb> | bradleymeck: is it `constructor-constructor`, and the code? |
17:33 | <bradleymeck> | yes in that case |
17:33 | <ljharb> | fun |
17:34 | <bradleymeck> | but other fun and more esoteric stuff like that does come up while i audit stuff |
17:52 | <bendtherules> | So, if libraries do Get/hasOwnProperty instead of general accessors - doesn't that fix it? |
17:52 | <bendtherules> | (if that's what they want Ofcourse) |
17:53 | <bendtherules> | given these are data manipulation libraries usually, that's a fair limitation |
17:59 | <devsnek> | bendtherules: the problem is that people write the code |
17:59 | <devsnek> | there are of course solutions |
18:00 | <devsnek> | but the solutions are only useful if people use them |
18:03 | <bendtherules> | Hmm, got it. So, there's no easy syntax for that |
18:03 | <bradleymeck> | bendtherules: yes it is largely a DX problem |
18:03 | <bradleymeck> | a variety of things can make this easier |
18:03 | <bradleymeck> | you can use null prototype objects is my main approach |
18:04 | <bradleymeck> | there are actually very few reasons to want to have an instance of Object |
18:04 | <bradleymeck> | so in the example tweet above adding __proto__:null to the literal fixes it |
18:04 | <bradleymeck> | but no one does that |
18:05 | <bradleymeck> | and security noise comes from the libraries doing dynamic access, so even if it is fixed you still get alerts |
19:13 | <bakkot_> | gotta fix `extends null` |
19:13 | <bakkot_> | should revisit that |
19:13 | <bakkot_> | one hairbrained idea I had for it was to make `Function.prototype` new-able |
19:13 | <bakkot_> | though I don't remember enough details to know why I thought that would fix the problem |
19:31 | <bendtherules> | bradleymeck: does proto null solve it though? You are going to want to call the inherited method, at some point - after all. |
19:31 | <bendtherules> | Also what about array literals? That will also finally lead to object in proto chain, right? |
19:32 | <ljharb> | it seems fine to me to make `super()` a no-op when super is null, why is that not workable? |
19:35 | <bendtherules> | ljharb: just thinking out loud, so should `super.xyz`still throw some error? |
19:41 | <ljharb> | bendtherules: yes, i'd expect so |
19:41 | <ljharb> | but i might expect `super?.xyz` to work |
19:41 | <bradleymeck> | bendtherules: thats fine, the problem is the inherited constructors, calling the method isn't a problem |
19:42 | <bradleymeck> | so yes, a null prototype on dynamic property access usually fixes the issue |
19:42 | <bradleymeck> | bakkot_: to my recollection most of the complaints are about minor edge cases |
19:43 | <bradleymeck> | bendtherules: you can access ([])['constructor'] just fine so idk what the question about them is |
19:43 | <bakkot_> | bradleymeck yeah mostly |
19:44 | <bendtherules> | bradleymeck: I meant, if i do `a={__proto__:null}`, then this solves security issue, but now i can't expect `a.toString` to work. What if i want that? |
19:44 | <bakkot_> | stop wanting that, presumably? |
19:44 | <bakkot_> | when would you want that to work? |
19:44 | <bakkot_> | that is, to work how it does by default |
19:45 | <bakkot_> | ljharb is "when super is null" a at-definition-time property or an at-evaluation-time property? remember that you can dynamically change a class's heritage |
19:45 | <ljharb> | bakkot_: evaluation time, for that reason |
19:46 | <ljharb> | bakkot_: definition-time is "it's a derived class, so super is allowed" |
19:46 | <bakkot_> | ok, so is super _required_ in your extends-null class? |
19:46 | <bendtherules> | I was thinking - with the same object, sometimes i want "safe mode" (i.e. i am dealing with user-generated value in this line) - so i'll write something like`obj[? prop1][? prop1]` |
19:47 | <ljharb> | bakkot_: yes, if i want to access `this` |
19:47 | <bakkot_> | ljharb that seems... very strange |
19:47 | <ljharb> | why? |
19:47 | <bendtherules> | but in next line, when i am calling toString, i can call `obj.toString()` directly |
19:47 | <ljharb> | `this` is only ever available in a derived class after calling super() |
19:47 | <bakkot_> | lj because there is no super class to call? |
19:47 | <bakkot_> | ljharb * |
19:47 | <ljharb> | there's whatever i put in `extends X` |
19:47 | <bakkot_> | in this case, `null` |
19:47 | <ljharb> | sure |
19:47 | <bakkot_> | ljharb right, well, the contention is that a class which extends null is not a derived class |
19:48 | <ljharb> | i'm basing my model on the syntax |
19:48 | <ljharb> | if it has "extends" it's a derived class |
19:48 | <ljharb> | (not that "derived class" is a good name for it, that's just the spec name) |
19:48 | <bakkot_> | that is a view one could hold, but not one I share or expect to be common |
19:49 | <ljharb> | i mean, i don't think "common" needs to apply with anything that changes the [[Prototyoe]] later |
19:49 | <ljharb> | what's common is never monkeying with the prototype at all |
19:49 | <bakkot_> | I am talking mainly about the mainline case, where you don't do that |
19:49 | <ljharb> | ok, so like `class extends null` or the equivalent |
19:49 | <bakkot_> | yup |
19:49 | <bakkot_> | `new class extends null { constructor(){ console.log(this); } } // works?` |
19:50 | <bakkot_> | per your above, no |
19:50 | <ljharb> | i'd assume it throws, right |
19:50 | <bakkot_> | that would surprise me |
19:50 | <ljharb> | it would throw with any other value in place of `null` there |
19:50 | <shu> | ljharb: what would you expect super() to do in that case? |
19:50 | <ljharb> | and since it's an expression, `null` is the same as `x` |
19:50 | <ljharb> | shu: nothing |
19:50 | <ljharb> | shu: just bind the `this` and install fields |
19:50 | <shu> | why is that less surprising to you than throwing, because the super class is clearly null? |
19:50 | <ljharb> | shu: iow super does two things right now, "invoke the superclass constructor" and "bind the receiver and install fields" |
19:50 | <bakkot_> | ljharb well, yes, because the other things you could put there would be expected to be _actual superclasses_, whereas null is, clearly, not |
19:51 | <ljharb> | because in my `extends null` class what i *want* is to install the fields |
19:51 | <ljharb> | and i want to control when that happens |
19:51 | <bakkot_> | ... well |
19:51 | <bakkot_> | stop wanting that? |
19:51 | <ljharb> | lol |
19:51 | <bakkot_> | sorry sorry |
19:51 | <shu> | i see, that's a different line of argument that folks have a model that all expressions in heritage positions are somehow semantically interchangeable |
19:51 | <bakkot_> | I mean to say, I don't find "I want to control when fields are installed on my extends-null class" to be a compelling use case |
19:51 | <ljharb> | surprise in an edge case seems less important to avoid than giving people what they want :-p |
19:52 | <ljharb> | bakkot_: that part's fair |
19:52 | <bakkot_> | ljharb right, well, the thing that I want is to not have to call `super` in my extends-null class |
19:52 | <bakkot_> | because, not being a subclass, I don't expect to have to |
19:52 | <bakkot_> | there is no superclass to call |
19:52 | <ljharb> | i see the difference in our thinking here: to me, if it has `extends`, it *is* a subclass |
19:52 | <bakkot_> | ... of... what? |
19:52 | <ljharb> | of whatever's on the RHS of `extends` |
19:52 | <bakkot_> | ... null? |
19:52 | <bakkot_> | null is not a class |
19:53 | <bakkot_> | this I believe |
19:53 | <ljharb> | sure |
19:53 | <ljharb> | `new class extends {} { constructor() { this; } }` throws too tho |
19:53 | <ljharb> | iow, "the RHS is a class" is already not a guarantee |
19:53 | <ljharb> | and the subclass rules still apply to everything simply because `extends` is present |
19:53 | <bakkot_> | wait, I don't understand this example |
19:54 | <ljharb> | why is null the one deviation? |
19:54 | <ljharb> | it extends an empty object |
19:54 | <bakkot_> | ljharb `{}` is not a class, which is why `super()` would throw |
19:54 | <ljharb> | sure |
19:54 | <ljharb> | but i didn't use super there |
19:54 | <ljharb> | i used `this` |
19:54 | <bakkot_> | sure, but |
19:54 | <ljharb> | and it threw because i never called super |
19:54 | <ljharb> | because it's a subclass, *because `extends` is there* |
19:54 | <bakkot_> | ok but it would also throw if you did call `super()` |
19:54 | <ljharb> | sure |
19:54 | <bakkot_> | I don't understand the relevance of this example |
19:54 | <ljharb> | so the part that i'm suggesting would be special for null is "what happens when you call super()" |
19:54 | <ljharb> | there is no scenario where `extends` is present and you'd not have to call super to access `this` |
19:55 | <bakkot_> | right, yes, I get that this is what you are proposing |
19:55 | <ljharb> | and i don't think it makes any sense to make `null` special for the latter case |
19:55 | <bakkot_> | our difference is, I don't think `null` is a class |
19:55 | <bakkot_> | meaning, I don't think one can have a subclass of `null` |
19:55 | <ljharb> | neither is most of the other things i can put on the RHS of `extends` |
19:55 | <bakkot_> | right, you can't have subclasses of those eitherr |
19:55 | <ljharb> | because "the thing it extends is a class" is currently irrelevant to whether `super` is required. |
19:55 | <shu> | ljharb: i also do not understand why your opinion is that "there is no scenario where `extends` is present and you'd not have to call super to access `this`" is such a desirable property to preserve |
19:55 | <ljharb> | shu: because the syntax is what developers see |
19:56 | <ljharb> | shu: they see extends, they know they need to call super |
19:56 | <ljharb> | shu: that's my argument anyways |
19:56 | <bakkot_> | that is not my intuition |
19:56 | <shu> | i simply disagree with that intuition |
19:56 | <bakkot_> | I see `extends null`, I do not expect to have to call `super`, because I do not think I am writing a derived class |
19:56 | <ljharb> | right but you might not see null there |
19:56 | <shu> | ...how? |
19:56 | <bakkot_> | this is technically true but I think not super relevant |
19:57 | <ljharb> | you might have a function that takes a superclass and returns a new class, and the user could pass null or a constructor |
19:57 | <bakkot_> | I expect the overwhelming majority of cases to put `null` there explicitly |
19:57 | <ljharb> | that's fair, i also expect that |
19:57 | <shu> | ah, i see, that would be a strange kind of class factory |
19:57 | <shu> | that sometimes generates a subclass and sometimes not? |
19:57 | <ljharb> | shu: i maintain it's a subclass if it has `extends` :-) |
19:57 | <bakkot_> | ljharb I still want to know what you think it is a subclass _of_ |
19:57 | <ljharb> | nothing, if it's null |
19:57 | <bakkot_> | what... does that mean |
19:57 | <ljharb> | its subclass-ness is not contingent on what it's subclassing |
19:58 | <bakkot_> | you and I do not share an understanding of the word "subclass", I see |
19:58 | <ljharb> | right |
19:58 | <shu> | ljharb: when you are writing new classes, do you think about the class hierarchy? |
19:58 | <ljharb> | another way to put it, is that to me a subclass is only a class body in which `super` can be used |
19:58 | <shu> | or do you only think about a stream of tokens? |
19:58 | <ljharb> | shu: sure |
19:58 | <ljharb> | i don't think about tokens at all, but i do think about what i'm typing, which includes the syntax |
19:59 | <shu> | okay, when is the point in time when you switch from thinking about your syntactic notion of "subclass" to the other one, where a subclass is a class in a hierarchy with a superclass? |
19:59 | <ljharb> | i either typed a base class, or a subclass, by virtue of whether i chose `extends` or not |
19:59 | <ljharb> | when i `new` it, i suppose? |
19:59 | <ljharb> | you're describing a runtime characteristic, not a definition time one |
19:59 | <shu> | is there any linkage between your two notions? |
19:59 | <shu> | this is just all very bizarre to me |
20:00 | <ljharb> | so what would be an alternative to "making extends null work" besides "super() where super is null is a runtime noop"? |
20:00 | <ljharb> | the constructor checks the Prototype before it evaluates, and if it's null it binds it like a base class? |
20:01 | <bradleymeck> | i'd rather lean towards the presence of `extends` at all makes it an explicit subclass of some kind |
20:01 | <rkirsling> | it doesn't make sense to call `extends null` subclassing; what you'd be saying is that there is _implicit_ subclassing when `extends` is omitted such that you'd like to be able to explicitly cancel it out |
20:01 | <ljharb> | rkirsling: not sure what that means |
20:01 | <shu> | ljharb: what is the difference in your mental model between `class C extends Object` and `class C` |
20:02 | <bradleymeck> | shu: to me, it is that you have used a different construct |
20:02 | <ljharb> | shu: the former is an explicit subclass and the latter isn't? |
20:02 | <bradleymeck> | i don't understand the idea that they are equivalent |
20:02 | <ljharb> | shu: i don't actually care that they both inherit from Object.prototype |
20:02 | <bradleymeck> | one requires super() |
20:02 | <ljharb> | ^ |
20:03 | <bradleymeck> | you made a choice by using 1 of the 2 constructs, and it causes a variation in how they need to be used |
20:03 | <rkirsling> | oof. it sucks that those are the same insofar as the word "subclass" is concerned and yet we have a thing about them that isn't the same :( |
20:04 | <bradleymeck> | rkirsling: they aren't the same is exactly the point of why i don't think they should be equated |
20:04 | <ljharb> | they do result in the same prototype chain. but they're not the same. |
20:04 | <bradleymeck> | to put it a different way, a non-subclass can simply be thought of as something that cannot call super() during coonstruction |
20:04 | <shu> | i see, interesting |
20:05 | <shu> | so you really have a purely syntactic notion of "subclass" |
20:05 | <ljharb> | yep |
20:05 | <rkirsling> | ouch |
20:05 | <bradleymeck> | shu: i wouldn't say syntactic |
20:05 | <ljharb> | that's the expectation the ES6 syntax sets up imo |
20:05 | <shu> | bradleymeck: no? |
20:05 | <bradleymeck> | but the equality of output doesn't tie me to the syntax being equal in intent |
20:05 | <shu> | bradleymeck: if the difference is not a ontological one in terms of hierarchical relationships but in whether super() throws or not |
20:06 | <shu> | bradleymeck: that seems purely syntactic to me |
20:06 | <rkirsling> | it's really unfortunate that we made `extends Object` require `super` :( |
20:06 | <ljharb> | if we hadn't, then we'd already know what to do with null |
20:06 | <ljharb> | and with `extends {}` or `extends false` |
20:07 | <bradleymeck> | shu: I'd agree that it is related to what the programmer wrote, but not necessarily that it is purely syntactic |
20:07 | bradleymeck | has to dig up a spec sadness |
20:07 | <shu> | ljharb: so by this logic, you would not be opposed (syntax budget concerns aside) to another syntax that isn't "extends null" |
20:08 | <shu> | presumably because there is no extant property about super throwing or not tied to any new syntax |
20:08 | <ljharb> | shu: absolutely correvt |
20:08 | <ljharb> | `null class` or something |
20:08 | <shu> | in general i feel like that's a dangerous constraint on design |
20:08 | <bradleymeck> | shu: i'd probably be in the same camp as being fine with other syntax but I would prefer `null` to be in the syntax |
20:09 | <ljharb> | shu: how so? |
20:09 | <shu> | i don't really care enough about this particular use case |
20:10 | <bradleymeck> | shu: I think trying to state that the current programmer divergence must preserve the behavior of being purely syntactical is odd is my main complaint about the argument |
20:10 | <shu> | ljharb: because to me, i would almost always prefer to make tradeoffs that favor ontological symmetry (what is or isn't a subclass in the class hierarchy sense, in this case) over syntactic symmetry, unless doing so prohibits performant implementations, say |
20:10 | <bradleymeck> | extends is just a signal that the class is different from a non-subclass |
20:10 | <shu> | ljharb: see our repeated semi-serious attempts to reclaim 'with' to be useful |
20:10 | <bradleymeck> | is there a real issue with not throwing for null as a class heritage? |
20:10 | <ljharb> | shu: i think i agree with you in principle, but class syntax failed that rubric at inception, because `extends false` eg is permitted |
20:11 | <ljharb> | shu: and similarly, `super` isn't permitted in a class without `extends`, even if that class changes its Prototype later |
20:11 | <shu> | bradleymeck: no, i think the issue is not requiring typing super() |
20:12 | <bradleymeck> | shu: i don't think there is a problem requiring super() when extending null personally |
20:12 | <rkirsling> | there's no way that shouldn't throw though :-/ |
20:12 | <bradleymeck> | i'd be kind of terrified if it wasn't required for null, since it is required for everything else |
20:12 | <ljharb> | tbh it's annoying to me to require `super()` in *every* case to me where i'm not changing the argument signature |
20:12 | <bradleymeck> | rkirsling: why |
20:12 | <ljharb> | rkirsling: yes, why |
20:12 | <ljharb> | rkirsling: "super" just means "make sure my instance is set up, somehow" |
20:13 | <ljharb> | that it invokes a constructor is an implementation detail |
20:13 | <shu> | i don't think that's how most people think of it... |
20:13 | <rkirsling> | it's a function call |
20:13 | <shu> | it is called "super" |
20:13 | <shu> | not "init" |
20:13 | <ljharb> | rkirsling: it's function-like, like import(). you can't pass it anywhere else. |
20:13 | <bradleymeck> | rkirsling: a function call to what? directly to the class heritage? it doesn't do a normal behavior when you call it |
20:13 | <bradleymeck> | I'd agree it has to be a call to *something* |
20:13 | <bradleymeck> | but not to the class heritage itself |
20:13 | <rkirsling> | yeah I mean I know it's a keyword but the idea is that you're calling a function, namely the constructor of the superclass |
20:14 | <devsnek> | auto call super if you don't call super seems too magical to me |
20:14 | <bradleymeck> | rkirsling: but it doesn't do that exactly |
20:14 | <ljharb> | devsnek: i agree |
20:14 | <rkirsling> | it would need to throw just as `x = null; x.y()` does |
20:14 | <devsnek> | and that's not just |
20:14 | <devsnek> | you don't call super |
20:14 | <ljharb> | rkirsling: sure. and in `extends null` the implied constructor is `function () { return { __proto__: null } }` |
20:14 | <devsnek> | it has to be, super isn't used |
20:14 | <devsnek> | or smth `if (false) { super() }` |
20:14 | <devsnek> | extends null has no useful behavior atm |
20:15 | <bradleymeck> | rkirsling: I don't understand the last comment |
20:15 | <bradleymeck> | you aren't calling a method of the heritage nor the heritage directly |
20:15 | <ljharb> | rkirsling: i'd be very surprised if people found that `class C extends null { constructor() { super(); this.a = 1; } } new C()` produces an instance of C, with an own `a` property set to 1, with a [[Prototype]] set to null, was confusing |
20:15 | <devsnek> | clearly what we need is |
20:16 | <devsnek> | a global `NullProtoObject` constructor |
20:16 | <rkirsling> | ljharb: it's not the produced thing that's confusing, it's the fact that `super()` doesn't throw |
20:16 | <rkirsling> | I find that wildly objectionable |
20:16 | <bradleymeck> | rkirsling: I still don't understand the claim it should throw |
20:17 | <bradleymeck> | cause super() clearly doesn't just do a function call |
20:17 | <rkirsling> | you can't call a method on null, I don't know what else to say |
20:17 | <ljharb> | rkirsling: that's not what you're doing |
20:17 | <ljharb> | rkirsling: `super()` isn't calling a method on null, it's not calling a method on anything - it's asking the engine to set up the class hierarchy |
20:17 | <bradleymeck> | rkirsling: you don't call methods on class heritage though, see `extends Object.setPrototype(function () {}, null)` |
20:17 | <ljharb> | that *may* involve invoking a constructor |
20:18 | <bakkot_> | ljharb I don't really agree with the claim that `extends false` is permitted |
20:18 | <ljharb> | bakkot_: it's syntactically permitted. obv `super()` would throw there |
20:18 | <bakkot_> | and therefore do not want to reason from that claim |
20:18 | <bakkot_> | ljharb sure but why does that... matter? |
20:18 | <ljharb> | bakkot_: if i don't use `this`, i can still return an object from the constructor and everything works fine |
20:18 | <ljharb> | because that's how classes work? |
20:19 | <ljharb> | i don't see how it makes sense to just pretend to ignore a bunch of existing semantics until it matches the mental model you want |
20:19 | <bakkot_> | my mental model is that JS is not typechecked, which means a lot of nonsense is allowed statically and throws only at runtime |
20:20 | <bakkot_> | you brought up this example in response to Shu's "i would almost always prefer to make tradeoffs that favor ontological symmetry over syntactic symmetry" |
20:20 | <bakkot_> | in particular, you claimed JS has failed at this because the syntax is allowed |
20:20 | <ljharb> | hm |
20:20 | <bakkot_> | I dispute this; I claim it also matters what is allowed _at runtime_ |
20:20 | <bradleymeck> | i'm just not clear that there is an ontological symmetry |
20:20 | <bakkot_> | and `extends false` is not |
20:20 | <bakkot_> | (unless you do weird things JS programmers are unlikely to need to think about) |
20:21 | <devsnek> | i just think of every feature in terms of abstracting all operands to variables |
20:21 | <bradleymeck> | super() does *something* and not a direct call of some kind to the class heritage |
20:21 | <ljharb> | bakkot_: ok, so then you'd prefer `this` to Just Work whenever the superclass either doesn't exist, or it's a base class, or is null? |
20:21 | <bakkot_> | ljharb concretely, I think `this` should work without calling `super()` first when you have `class {}` or `class extends null {}` |
20:21 | <ljharb> | bakkot_: meaning that the impl would have to check the prototype chain the first time `this` is referenced in any class, instead of only when super is called in a derived/never in a base? |
20:21 | <ljharb> | right |
20:21 | <devsnek> | i wouldn't mind `extends null` meaning you don't call super |
20:21 | <bakkot_> | ljharb I am not making claims about what should happen outside of those two cases |
20:21 | <devsnek> | but others would |
20:22 | <bradleymeck> | bakkot_: should or must? |
20:22 | <ljharb> | bakkot_: ok, seems like we'd need to figure that out tho |
20:22 | <bakkot_> | ljharb of course, yes |
20:22 | <bakkot_> | I just want agreement on the core, happy-path semantics first |
20:22 | <bakkot_> | before we worry about the edge cases |
20:22 | <ljharb> | i don't think the one can come without the other |
20:22 | <bakkot_> | uh |
20:22 | <bakkot_> | why? |
20:23 | <ljharb> | so with changing the proto later, even in `class {}`, what happens the first time `this` is referenced? |
20:23 | <devsnek> | people will argue about `function factory(S) { return class extends S { constructor() { super(); } } }` |
20:23 | <bakkot_> | hang on, before I respond to that, I want to know why we need to talk about the edge cases before agreeing on the core semantics |
20:23 | <ljharb> | bakkot_: because in classes, i don't think they're separable |
20:24 | <ljharb> | the edge cases are part of the core semantics to me |
20:24 | <bakkot_> | ljharb well |
20:24 | <bakkot_> | I do not agree with that |
20:24 | <bakkot_> | that's... pretty much a contradiction in terms |
20:24 | <ljharb> | for classes specifically, i think everything's so entangled and intertwined that it can't really be separated |
20:24 | <ljharb> | to me, class fields discussions bore that out as well |
20:24 | <bradleymeck> | bakkot_: i think the presence of extends forcing super() is really brutal to try and claim is decoupled |
20:25 | <bakkot_> | bradleymeck I'm not claiming it's decoupled? |
20:25 | <bakkot_> | that's the case I'm talking about |
20:25 | <bradleymeck> | i'm losing something via reading this |
20:26 | <bakkot_> | bradleymeck my core claims are "`this` should work without calling `super()` first when you have `class {}` or `class extends null {}`" and "I want to get agreement on these cases before trying to reason about what happens when you change the prototype dynamically" |
20:27 | <devsnek> | bakkot_: there were people who objected to a super call not aligning exactly with the existence of `extends` regardless of what the value actually is |
20:28 | <bakkot_> | devsnek yes, ljharb has expressed that opinion |
20:28 | <ljharb> | (and while i'm one of them i don't think i ever said as much in plenary) |
20:28 | <bakkot_> | which is why I'm here contesting it |
20:28 | <ljharb> | so there's a number of folks who think that way |
20:29 | <bakkot_> | ljharb anyway, would you be willing to talk about my claims without trying to get into what happens in edge cases? |
20:29 | <bradleymeck> | bakkot_: can you explain why it must not require calling super, while extends Object must? |
20:29 | <bakkot_> | because if you agree those are good goals but don't think there's a good way to achieve them, that's different from disagreeing with the goals |
20:30 | <ljharb> | bakkot_: i'm not sure what more we'd talk about :-) i hear your suggestion, and altho it conflicts with my intuition about extends+super, to me the only way i'd be able to consider ignoring that conflict is if i understand the edge cases too |
20:30 | <bakkot_> | bradleymeck I don't think anyone writes `extends Object` and I don't actually care if that particular case requires calling `super` or not |
20:30 | <ljharb> | i do disagree with the goal, but not in a forever-intractable kind of way |
20:30 | <ljharb> | i think "avoiding writing super()" is a non-goal |
20:30 | <ljharb> | (if that were a goal, then most of my "actual" subclasses wouldn't have required it either, including every react component) |
20:31 | <bradleymeck> | i'd agree that avoiding verbosity isn't a real goal, as long as usability isn't too impacted, you already are doing additional burden by requiring `extends null` |
20:32 | <bakkot_> | yes, my objection is not "this is a burden", it's "this requirement breaks my ontology by suggesting that an extends-null class is a subclass when it is not" |
20:32 | <bradleymeck> | bakkot_: why is it not i guess is the follow up |
20:32 | <ljharb> | to paraphrase a smart person, "stop wanting that ontology?" |
20:32 | <bakkot_> | bradleymeck of what? |
20:32 | <ljharb> | :-p |
20:32 | <bradleymeck> | bakkot_: why is extending null not a subclass if it has a heritage |
20:32 | <bakkot_> | ljharb I am willing to give it up if there is reason to believe it is unusual! |
20:33 | <bakkot_> | ljharb but fwiw I cannot find any definition of "subclass" anywhere for which "it is a subclass, but not of anything" would make sense |
20:33 | <ljharb> | i would be very surprised if muggle devs thought about the definition of subclass at all |
20:33 | <bradleymeck> | lack of heritage does affect things like replacements of heritage causing errors |
20:33 | <bakkot_> | strong disagree |
20:33 | <shu> | everyday practitioners for sure think about class hierarchy? |
20:33 | <ljharb> | i believe they think their class either extends something or doesn't, and if it does, it requires super |
20:34 | <ljharb> | shu: yes but not in a strict "this is a subclass" sense |
20:34 | <rkirsling> | I don't think devs think that |
20:34 | <bradleymeck> | i'm fine with the idea that people think about some hierarchy but i don't understand that having a heritage is seen as not being a subclass |
20:34 | <rkirsling> | if anything they forget that it's not simple OOP |
20:34 | <bakkot_> | bradleymeck having a heritage _specifically from null_ is not being a subclass |
20:34 | <bakkot_> | you cannot be a subclass without having a superclass |
20:34 | <ljharb> | rkirsling: sure but those people also aren't going to be extending null |
20:34 | <bakkot_> | `null` is not a superclass |
20:35 | <rkirsling> | true |
20:35 | <ljharb> | ok so then maybe "subclass" is the wrong term |
20:35 | <ljharb> | if it has `extends`, the spec calls it a derived class |
20:35 | <shu> | ljharb: this past hour of argumentation has kinda hinged on your using a spec author's understanding of "subclass" and say that people don't understand it that way |
20:35 | <ljharb> | derived classes require use of super |
20:35 | <bradleymeck> | bakkot_: can you explain why it isn't something that can be derived from? |
20:35 | <ljharb> | shu: hm, let me think about that one |
20:35 | <bradleymeck> | shu: to be clear I do think there is variation on the presence of a heritage that is attempting to be ignored, ljharb isn't purely alone |
20:36 | <bakkot_> | bradleymeck I didn't say "cannot be derived from", I said "is not a class" |
20:36 | <bakkot_> | *is not a superclass |
20:36 | <bakkot_> | the reason there being, it is not a class |
20:36 | <bakkot_> | the reason for that being that it... isn't... a class? I don't know how to justify that onoe |
20:36 | <shu> | ljharb: all i'm saying is almost all practitioners, by my observation and experience, certainly think about subclassing. what they don't think about is what you defined "subclass" to mean, so yes i agree we should stop using "subclass" in this argument at least. |
20:37 | <bradleymeck> | bakkot_: if a class is derived from something it isn't a subclass? |
20:37 | <rkirsling> | maybe like "explicit extension" |
20:37 | <bradleymeck> | in particular i think the claim that `extends` is purely syntactic is false |
20:37 | <bradleymeck> | `class X { constructor() { this.x = 'x'; } }; X.__proto__ = function Y() {this.y = 'y';}; new X();` |
20:38 | <bradleymeck> | vs |
20:38 | <bradleymeck> | `class X extends Object { constructor() { super(); this.x = 'x'; } }; X.__proto__ = function Y() {this.y = 'y';}; new X();` |
20:39 | <ljharb> | that feels like a pretty compelling example to me |
20:39 | <shu> | bradleymeck: what is that trying to show |
20:39 | <bradleymeck> | shu: one of those does add this.y, one does not |
20:40 | <bradleymeck> | both have a class heritage value of Object |
20:40 | <bakkot_> | bradleymeck: in the spirit of avoiding the term "subclass", I would regard a class which `extends null` as sitting at the top of its class hierarchy, the same way that a class which does not `extend` anything does |
20:40 | <bakkot_> | since it sits at the top of its class hierarchy, I would be surprised if I had to call `super()` |
20:41 | <bradleymeck> | bakkot_: i am unclear on *why* it is at the top |
20:41 | <bakkot_> | bradleymeck uh |
20:41 | <bakkot_> | there is no possible thing which could be above it? |
20:41 | <bradleymeck> | null seems to be at the top |
20:41 | <bakkot_> | null is not a class |
20:41 | <bakkot_> | null cannot be in a class heirarchy |
20:42 | <ljharb> | i mean, the hierarchy is between [[Prototype]] values, which are all objects or null |
20:42 | <bakkot_> | LSV cannot hold for `null` and any `class` under almost any circumstances |
20:42 | <ljharb> | so null is part of a prototype hierarchy - at the top |
20:42 | <bakkot_> | *LSP |
20:42 | <bradleymeck> | i mean, we can argue it isn't a class but that class does have a heritage |
20:42 | <ljharb> | a "class hierarchy" is just a combination of the constructor's prototype hierarchy, and its prototype object (and its prototype hierarchy) |
20:42 | <shu> | no! that's the core confusion i think |
20:42 | <bakkot_> | ljharb strong disagree |
20:43 | <ljharb> | do ES5 constructors have a class hierarchy? |
20:43 | <bakkot_> | class hierarchy is a concept which exists independent of language |
20:43 | <bakkot_> | it is a concept prior to concrete programming languages |
20:43 | <rkirsling> | ^ |
20:43 | <shu> | there's a programmer's "theory" of a class hierarchy, it's the mental map of building blocks of a program and how things are divided up |
20:43 | <shu> | ljharb and bradleymeck instead are arguing from purely mechanical grounds of what JS happens to do and how that is inconsistent |
20:44 | <shu> | because JS is a dynamic language, inconsistencies and weirdness will invariably be possible |
20:44 | <bradleymeck> | shu: i don't care what you call it, it can be a base class but it has a heritage |
20:44 | <shu> | ... |
20:44 | <ljharb> | bakkot_: then "extends null" as a concept just doesn't fit into your concept of class hierarchy imo |
20:44 | <bradleymeck> | shu: to be clear i don't care if null is considered outside the heirarchy |
20:44 | <bakkot_> | ljharb yes, that is my claim |
20:44 | <ljharb> | even the existence of a null [[Prototype]] doesn't seem like it's part of this class hierarchy concept |
20:44 | <ljharb> | which would mean it's not an appropriate concept to apply to JS |
20:45 | <bakkot_> | ljharb disagree |
20:45 | <bradleymeck> | i simply don't understand this logic that heritage must match a class hierarchy and be a direct call of some kind to it |
20:45 | <bakkot_> | I don't think every feature in a language needs to be represented perfectly by some concept for that concept to be useful |
20:45 | <bradleymeck> | both of those seem to be invalidated both at runtime and to some extent at parse time |
20:45 | <shu> | bradleymeck: i don't think that's the logic |
20:45 | <ljharb> | bakkot_: nor does a useful concept need to constrain every aspect of the language's design |
20:45 | <bakkot_> | ljharb right |
20:45 | <bakkot_> | but |
20:46 | <bradleymeck> | shu: what is the logic? avoid using subclass |
20:46 | <bakkot_> | that does not mean you are free to ignore it |
20:46 | <bakkot_> | bradleymeck I don't know what you mean by "heritage", I am realizing |
20:46 | <shu> | bradleymeck: the logic is that *unless* you do edge casey things like runtime __proto__ mutation, not requiring super() in `extends null` classes matches up with the general OOP model of class hierarchies |
20:46 | <shu> | bradleymeck: and that is a valuable property |
20:47 | <ljharb> | bakkot_: “not constrain” definitely means “free to ignore” :-p |
20:47 | <bradleymeck> | shu: can you explain what we get for that property? |
20:47 | <shu> | ljharb: that is also just not true! |
20:47 | <shu> | bradleymeck: we get a happy path for common usage? the normal 80-20 thing |
20:47 | <ljharb> | it doesn’t mean it’s wise to ignore it ofc |
20:47 | <bradleymeck> | shu: can you explain that more |
20:47 | <bradleymeck> | why is not using super() the explicit happy path |
20:47 | <bradleymeck> | like, what does that mean |
20:48 | <bakkot_> | bradleymeck the happy class is not that you are avoiding `super` |
20:48 | <bakkot_> | its that your usage of the language matches the concepts in your head |
20:48 | <bakkot_> | in particular, that a class which extends `null` is at the top of its class heirarchy |
20:48 | <shu> | bradleymeck: like, when i'm programming in an OO way, i think about classes and how i organize my code, right? |
20:48 | <shu> | bradleymeck: i don't start by first saying "let me think of the full semantics of JS's classes" |
20:48 | <shu> | i think about organization |
20:48 | <bradleymeck> | bakkot_: i don't understand that claim, i don't have a concrete mental model i try to force all my programming into |
20:49 | <shu> | this isn't concrete |
20:49 | <bakkot_> | I will let shu say the thing I would say |
20:49 | <shu> | it's like a rough organizational idea? |
20:49 | <bradleymeck> | shu: ok, and having super() would break it, and in a way that isn't easily learned/debugged? |
20:49 | <bradleymeck> | what about it breaks the model |
20:49 | <shu> | bradleymeck: oh i don't know about the ease claim, it seems easy enough to fix |
20:50 | <shu> | bradleymeck: it's not a binary break-or-not-break |
20:50 | <shu> | it makes the mapping more difficult |
20:50 | <bradleymeck> | what would it mean if a base class is calling super() |
20:50 | <bradleymeck> | from your model |
20:50 | <shu> | i don't know what it would mean |
20:50 | <bakkot_> | bradleymeck it would not mean anything |
20:50 | <bakkot_> | `super()` is a thing for subclasses to do |
20:51 | <bradleymeck> | bakkot_: it would be non-sensical or something else? |
20:51 | <bakkot_> | in particular, for them to invoke their superclass |
20:51 | <bradleymeck> | bakkot_: and if they don't have superclass? |
20:51 | <bakkot_> | bradleymeck this question is like "what would it mean if orange was a sound" |
20:51 | <bradleymeck> | to my recollection abstract superclasses etc might be ellided in ye olden days |
20:51 | <bradleymeck> | bakkot_: i'd disagree |
20:51 | <shu> | bradleymeck: hold on, the "if they don't have a superclass" is the turning point where we diverge, i think |
20:52 | <shu> | bradleymeck: *you*, as a JS expert, know that the congruence i want for `super` in my hypothetical is _already not true_ |
20:52 | <shu> | bradleymeck: the contention is that the cases where it is already not true are, i think, rare, like runtime prototype mutation |
20:52 | <bradleymeck> | shu: i'm thinking of me in 11th grade learning Java |
20:52 | <ljharb> | bakkot_: ok what about this: what if `super()` always throws when the superclassish is null, but `super?.()` doesn't throw when it's null (but does set up the `this`) |
20:52 | <ljharb> | bakkot_: would that break you and shu's mental model? |
20:53 | <bradleymeck> | shu: a lot of what you are stating is really confusing, just like polymorphism is confusing, let alone things like multiple inheritance |
20:53 | <bradleymeck> | but we are familiar with topics here |
20:53 | <rkirsling> | wait `super?.()` doesn't (and shouldn't) exist since it's a keyword though |
20:53 | <bakkot_> | ljharb again, my core claim is that a class which extends `null` should not need to call `super`; I am somewhat less worried about what happens in other cases |
20:53 | <devsnek> | i was not ready for `super?.()` |
20:54 | <devsnek> | but i don't hate it |
20:54 | <ljharb> | rkirsling: it could certainly be permitted |
20:54 | <shu> | bradleymeck: single inheritance is confusing? |
20:54 | <bradleymeck> | shu: for me it was |
20:54 | <bradleymeck> | to some extent... is? |
20:54 | <bakkot_> | bradleymeck in 11th grade Java, you would not call `super()` in a class which does not have a superclass |
20:54 | <bakkot_> | `super()` is a thing for subclasses to do, and a subclass cannot not-have a superclass |
20:55 | <bradleymeck> | bakkot_: that was learned and still seems to be completely fine if i had been forced to call it |
20:55 | <rkirsling> | why would it be completely fine though? |
20:55 | <bradleymeck> | it wasn't inherent in the fact that a class inherited |
20:55 | <rkirsling> | it refers to something nonexistent |
20:55 | <devsnek> | couldn't you argue if you don't specify `extends` it defaults to Objcet |
20:55 | <bradleymeck> | rkirsling: why does super() have to do something? |
20:55 | <bradleymeck> | thats really a crux of the argument |
20:56 | <bradleymeck> | if super() doesn't need to do anything... why do you have to avoid calling it |
20:56 | <bakkot_> | bradleymeck here we are arguing about whether `super` should be required; if it is required. it definitely does do something |
20:56 | <rkirsling> | devsnek: yeah that's my view but I guess it's messy since extends syntactically requires super |
20:56 | <bradleymeck> | bakkot_: why |
20:56 | <bradleymeck> | what might be better a question |
20:56 | <rkirsling> | bradleymeck: a no-op is still a something |
20:56 | <bakkot_> | bradleymeck um. how can it be required and also not do something? at the very least "make the class work"? that is a thing that it has done. |
20:58 | <shu> | i think i am going to do some reviews |
20:58 | <rkirsling> | I understand that super is _more_ than just a function call but I don't buy that it's ever not one, just because the function is an implicit no-op |
20:58 | <bradleymeck> | bakkot_: to clarify a bit, i don't see a no-op as being a claim that it does something and equating lack of extends and extends Object to be very consistent |
20:59 | <bakkot_> | bradleymeck I am not dead-set against it being a no-op. what I am against is it being required. |
20:59 | <bakkot_> | also, like I said, I don't really care about the `extends Object` case and am happy to explore whatever semantics there would make people happy |
20:59 | <devsnek> | are there use counters for `extends null` |
20:59 | <bradleymeck> | bakkot_: I'm very unclear on why it must not be required |
21:00 | <bradleymeck> | devsnek: there was at one point, but it is so error ridden it was terribly low when aklein did it |
21:00 | <bakkot_> | devsnek `extends null` currently results in a thing which is impossible to instantiate, which is almost certainly not the thing anyone wants |
21:00 | <bakkot_> | bradleymeck well, I don't know if I can say it any more clearly than it was said above, but I'll give it one more shot. |
21:00 | <devsnek> | i'm thinking we might just throw if its null |
21:01 | <devsnek> | remove the special case for null rather |
21:01 | <ljharb> | devsnek: it's not really a special case right now tho, is it? |
21:01 | <devsnek> | it is |
21:01 | <bradleymeck> | bakkot_: thanks, I'm just very confused on what this class heir-achy idea in some circle i've not participated in is being broken by :( |
21:02 | <devsnek> | ljharb: ClassDefinitionEvaluation 5.e |
21:02 | <bradleymeck> | i never had this mandate in my college that base classes must never do some sort of super, just that they don't |
21:03 | <bradleymeck> | they must not inherit |
21:03 | <devsnek> | the easy way out here would be to specify that Function.prototype returns Object.create(null) when [[Construct]]ed |
21:03 | <bradleymeck> | definitely cannot do that |
21:04 | <devsnek> | why not |
21:04 | <bradleymeck> | i've certainly seen code that uses new Function |
21:04 | <bradleymeck> | oh you mean the proto itself |
21:04 | <devsnek> | yes |
21:04 | <bradleymeck> | mmm |
21:04 | <bradleymeck> | odd |
21:04 | <devsnek> | when you do `class extends null` |
21:04 | <devsnek> | the parent is `Function.prototype` |
21:04 | <devsnek> | which is why it can't be instantiated |
21:06 | <ljharb> | if you did that "easy way out", wouldn't one expect `instanceof Function.prototype` to be true? |
21:06 | <devsnek> | not inherently? |
21:06 | <devsnek> | the result of `new X` doesn't have to fulfill `instanceof X` |
21:07 | <bradleymeck> | ljharb: i mean builtin protos are weird already, some are instances of their function, some are regular objects, some have the brand of their function |
21:07 | <ljharb> | i feel like that's still an expectation people would hold in the common case |
21:07 | <ljharb> | if we're going with "null isn't part of the class hierarchy" as an expectation |
21:08 | <devsnek> | i think all the reasonable alternatives are listed in the pr somewhere |
22:26 | <bakkot_> | bradleymeck sorry, I had a meeting before I could finish writing this up |
22:27 | <bakkot_> | anywhere here: https://gist.github.com/bakkot/d931e4d67a4d44ff79261d3699621cc8 |
22:28 | <bakkot_> | these are all things that I think, but also concretely I think they correctly describe the intuitions of most people who use `class` in languages like JS, Java, python, C++, etc |
22:37 | <rkirsling> | bakkot_: 💯 |