01:01
<ptomato>
another heads up for delegates, about Temporal. we got another spec bug report from two implementors today, and while investigating that one, Justin found another spec bug. I've added the fixes to the slides that I will present on Wednesday, but again since they are late additions, we understand if delegates will need more time to consider them.
03:05
<jschoi>
Was there no CoC Committee update in the first session? It was on the agenda, but I didn’t hear any update and it’s not in the notes.
08:43
<sarahghp>
No, I think the plan was to have it later.
08:57
<Rob Palmer>
The Halloween edition of TC39 plenary will begin in 3 mins.
08:59
<Surma>
👻
09:03
<nicolo-ribaudo>
I forget it every time: I don't have to fill the form every day, right?
09:04
<Rob Palmer>
correct, nicolo - we keep the same password
09:04
<Rob Palmer>
we just need each attendee to fill it in once
09:05
<nicolo-ribaudo>
Ok thanks!
09:10
<sarahghp>
(I had to do it twice because if you don't open matrix in a new window or save the info, you can't get back to it 🙈)
09:16
<Tierney Cyren>
oh hi Surma
09:16
<Surma>
👋 Tierney Cyren
09:17
<Tierney Cyren>
glad to see you here :)
09:18
<devsnek>
👋hello
09:20
<bakkot>
for the notes: frank said 3 PRs, but I can't figure out what the third one was
09:20
<bakkot>
anyone know?
09:20
<rbuckton>
That looked like it worked.
09:20
<bakkot>
there was the non-continuous weekend one and the annex A one and then a third one
09:21
<ryzokuken>
"canonical"?
09:21
<bakkot>
ah yes thanks
09:23
<devsnek>
queue may be confused https://gc.gy/285b32a5-6cd1-4c10-9e9a-8a300f754897.png
09:26
<devsnek>
an engine could probably do nice stack traces like that itself, no need to include pos in the error
09:30
<devsnek>
shu: raw returns some magic object that tells json.stringify to use the string as source instead of a string
09:30
<rbuckton>
In the cases where I have needed position information in JSON, I've generally had to rely on something like the TypeScript parser and AST, even in a purely JS project. My use cases so far have been fairly niche, such as doing minor updates to an existing JSON file while preserving the source formatting (usually for JSON files intended to be both human and machine readable such as package.json).
09:39
<bakkot>
syg: right now, JSON.stringify of an object without a toString is guaranteed to do the thing you expect absent a reviver, but if the symbol is shared that's no longer true
09:46
<bakkot>
Michael Ficarra: I am strongly opposed to having the marker be an object with a symbol here because it implies that the engine will need to do a lookup on every object
09:46
<bakkot>
which is observable
09:46
<bakkot>
it's simpler in the sense of "fewer kinds of object in the language", true, but not in terms of "how much observable behavior there is", and the second thing seems like the thing we should be minimizing most of the time
09:48
<Michael Ficarra>
bakkot: fair point
09:49
<shu>
that's a good point
09:49
<shu>
though i don't think a particular problem for performance in the common case
09:49
<Michael Ficarra>
bakkot: how do we ensure these exotic marked objects don't usefully escape the callback?
09:49
<devsnek>
you don't
09:49
<Michael Ficarra>
is the value of the slot fresh?
09:50
<devsnek>
oh you mean per-invocation
09:50
<devsnek>
in terms of spec language you could say its {[[Index]]: n, [[String]]: s}
09:50
<devsnek>
and expect that index back
09:50
<devsnek>
or you could expect the whole object i guess
09:50
<devsnek>
cuz it has identity
09:52
<bakkot>
Michael Ficarra: you can't ensure they don't escape, but like devsnek says you have a field which indicates which invocation it is
09:52
<bakkot>
s/field/internal slot/
09:52
<shu>
syg: right now, JSON.stringify of an object without a toString is guaranteed to do the thing you expect absent a reviver, but if the symbol is shared that's no longer true
is that something that's generally depended upon?
09:52
<devsnek>
let thing;
let raw = (v) => {
  thing = { v };
  return thing;
};
const result = callback(key, value, { raw });
if (thing && result === thing) {
09:52
<Michael Ficarra>
okay I mean a counter is just a fresh value, so I'm fine with that
09:52
<shu>
like, wouldn't the new guidance be "here's another hook we need, if you hook into it, well, you get hook behavior"
09:52
<bakkot>
and stringify encounters an object with this slot holding a different value than for the current invocation it can throw
09:53
<devsnek>
what happens if you call raw multiple times in one callback
09:53
<Michael Ficarra>
devsnek: same index
09:53
<devsnek>
i feel like that should work, you could compose a sub-object
09:53
<nicolo-ribaudo>
Tbh I would expect the argument to raw to always be a string
09:53
<nicolo-ribaudo>
Otherwise it's not raw
09:53
<devsnek>
no i mean like
09:54
<devsnek>
(key, value, { raw }) => ({ a: raw(foo), b: raw(bar) })
09:54
<nicolo-ribaudo>
uh ok, that makes sense
09:55
<nicolo-ribaudo>
For example to represent a class Fraction { #numerator: bigint; #denominator: bigint }
09:55
<devsnek>
ye you could put a tojson on that
09:55
<devsnek>
which uses raw
09:56
<bakkot>
toJson wouldn't get passed raw
09:56
<bakkot>
I think
09:56
<bakkot>
unless we do the global well-known symbol approach
09:56
<devsnek>
why does it matter which approach
09:57
<nicolo-ribaudo>
Can't toJSON receive a parameter even if it's not global?
09:57
<devsnek>
i think tojson should get to participate
09:57
<nicolo-ribaudo>
Can't toJSON receive a parameter even if it's not global?
(it could break something?)
09:57
<bakkot>
I mean it could but it doesn't in the current proposal
09:57
<bakkot>
also if we're going to let toJson participate I think a well-known symbol would make more sense anyway
09:58
<bakkot>
since the point of having the per-invocation value is to ensure the stringifier callback has control
09:58
<bakkot>
and toJson is for giving the objects being serialized control
09:59
<bakkot>
though, I guess a global well-known symbol means we'd be imposing a cost on all existing serialization calls, which seems bad
10:01
<shu>
extra get per object in the graph to be stringified does seem bad
10:01
<shu>
though isn't that an issue even with a per-stringify symbol
10:02
<bakkot>
yeah
10:02
<bakkot>
not with an opaque object though!
10:03
<shu>
well, not an extra get but an extra slot check, which ought to be much cheaper, yes
10:03
<bakkot>

so, ok, here is an alternate possible design which I don't hate:

add a new JSON.rawString which returns an opaque, frozen, null-prototype object with a new internal slot holding a string (which has been checked to be valid JSON - maybe even a valid JSON primitive?). this can be called by any code. if JSON.stringify encounters such an object, it serializes it to its internal slot

10:03
<bakkot>
then toJson can use this function, and revivers can as well
10:04
<bakkot>
basically just the "allow it to be not per-invocation" approach, but with internal slots instead of symbols
10:04
<shu>
+1 from me. if the opaque object has gensym behavior it also complicates the performance of the slot check
10:05
<bakkot>
"has gensym behavior"?
10:05
<devsnek>
https://gc.gy/d9ff7617-8eb7-4039-81bf-ac0201805137.png
10:05
<shu>
the fresh minting behavior where each invocation of stringify has a new kind of opaque object only valid for that invocation
10:06
<devsnek>
gensym = generate symbol?
10:06
<shu>
yeah
10:06
<shu>
it's the scheme thing that returns fresh symbols
10:09
<bakkot>
I know what gensym is but am confused about how it affects GC here
10:10
<bakkot>
oh, unless you're thinking about the GC of the underlying string?
10:10
<bakkot>
I would just not worry about the fact that holding the opaque object holds the string, I guess
10:10
<shu>
no not gc
10:11
<shu>
i meant the "is this one of them opaque objects" checks
10:11
<bakkot>
oh
10:11
<bakkot>
I mean, you check if it has the slot, and then if it does you check if the value for the slot matches the value for the current invocation
10:12
<bakkot>
seems not so bad
10:12
<bakkot>
and it's just the presence-of-slot check which gets paid by code not using this capability
10:14
<shu>
agree, seems not so bad
10:15
<Michael Ficarra>
yeah I favour this raw function that returns an object with an internal slot with index (or two such slots) option now
10:15
<bakkot>
github issue: https://github.com/tc39/proposal-json-parse-with-source/issues/19
10:16
<bakkot>
Michael Ficarra: https://github.com/tc39/proposal-json-parse-with-source/issues/18#issuecomment-951789801
10:16
<shu>
Michael Ficarra: what index? the per-invocation counter?
10:16
<shu>
ehh, i'd still rather we just have global
10:17
<rbuckton>
The only issue I see with an exotic object is if you write any custom JSON serialization logic today in a replacer/reviver, you would need to be able to test whether the object is one of these exotic objects so you can pass it through rather than trying to convert it yourself.
10:17
<Michael Ficarra>
yes, per-Json-stringify counter and possibly a per-callback counter
10:17
<shu>
i am against a per-callback counter
10:17
<Michael Ficarra>
that's probably fine shu
10:18
<shu>
i am also against a per-invocation counter and favor bakkot's concrete alternative, to be clear
10:18
<shu>
but i am more against per-callback counter
10:18
<rbuckton>
So you need both a JSON.rawSting and a JSON.isRawString, otherwise it comes harder to write serializers/deserializers.
10:18
<devsnek>
what do you do with isRawString
10:19
<rbuckton>
If you do custom serialization of any kind you need to know if the object you get in your custom serializer/replacer is this special exotic object or some other object.
10:20
<nicolo-ribaudo>

fwiw, in the R&T repository there has been a bunch of discussion about tagged strings (https://github.com/tc39/proposal-record-tuple/issues/258#issuecomment-947115796). This feels similar, since it's a string tagged with JSON.rawString.

(we are not looking to include tagged strings in the R&T proposal)

10:20
<devsnek>
why do you need to know
10:21
<devsnek>

fwiw, in the R&T repository there has been a bunch of discussion about tagged strings (https://github.com/tc39/proposal-record-tuple/issues/258#issuecomment-947115796). This feels similar, since it's a string tagged with JSON.rawString.

(we are not looking to include tagged strings in the R&T proposal)

this issue makes me very sad, previously we said that Box(v) should always work without you having to care what v is
10:22
<rbuckton>

A naive replacer might do:

function serializer(value) {
  if (typeof value === "object") {
    if (Array.isArray) { ... }
    const result = {};
    for (const key in value) { ... } 
    return result; // uh oh, we've replaced the exotic object with an empty normal object.
  }
  ...
}
10:22
<rbuckton>
If there's no way to test, you have to expect to pass through objects with no keys, which adds complexity.
10:23
<bakkot>
rbuckton: JSON.isRawString = o => o && typeof o === 'object' && !(toJSON in o) && !['{', '['].includes(JSON.stringify(o))
10:23
<Michael Ficarra>

fwiw, in the R&T repository there has been a bunch of discussion about tagged strings (https://github.com/tc39/proposal-record-tuple/issues/258#issuecomment-947115796). This feels similar, since it's a string tagged with JSON.rawString.

(we are not looking to include tagged strings in the R&T proposal)

I really don't like attaching state to strings or any object in a way that is not only immediately consumed
10:23
<Michael Ficarra>
if we do that, we'd have to at least have a testing function like what Ron's talking about
10:23
<Michael Ficarra>
but then you force anyone using this feature to test anything that goes into their returned object
10:23
<bakkot>
rbuckton: JSON.isRawString = o => o && typeof o === 'object' && !(toJSON in o) && !['{', '['].includes(JSON.stringify(o))
(assuming that isRawString enforces that its argument is a primitive JSON string or digit sequence)
10:24
<rbuckton>
The upside of a symbol is its something you can check for.
10:24
<bakkot>
(to be clear, not saying this is great, just saying that it's possible-ish without a new function)
10:24
<nicolo-ribaudo>
rbuckton: JSON.isRawString = o => o && typeof o === 'object' && !(toJSON in o) && !['{', '['].includes(JSON.stringify(o))
A JSON.rawString("{}") would be observabily the same as Object.freeze({ __proto__: null })
10:24
<rbuckton>
bakkot: So, your suggestion is to JSON.stringify an arbitrarily complex/large object that may have its own nested toJSON just to check for an exotic raw string object?
10:24
<bakkot>
nicolo-ribaudo: "assuming that isRawString enforces that its argument is a primitive JSON string or digit sequence"
10:25
<nicolo-ribaudo>
nicolo-ribaudo: "assuming that isRawString enforces that its argument is a primitive JSON string or digit sequence"
Whops, I didn't read it
10:25
<bakkot>
rbuckton: you can add Object.keys(o).length === 0 if you're worried about that
10:25
<rbuckton>
It would have to be much more robust, checking for null prototype, no properties, etc.
10:26
<rbuckton>
bakkot: More like Object.getPrototypeOf(o) === null && Object.getOwnPropertyNames(o).length === 0 && Object.getOwnPropertySymbols(o).length === 0 etc.
10:27
<bakkot>
unless you're concerned about proxies, I don't think you need those additional checks
10:27
<bakkot>
no non-exotic object can have the behavior that toJSON is absent and also !['{', '['].includes(JSON.stringify(o)[0])
10:27
<rbuckton>
In which case I point you at Array.isArray, which is better (and more reliable) than o => typeof o === "object" && o !== null && Object.prototype.toString.call(o) === "[object Array]"
10:28
<rbuckton>
I thought rawString could return a string that contains any valid JSON. Couldn't you do JSON.rawString("{}"), and wouldn't that fail the check?
10:29
<bakkot>
rbuckton: "assuming that isRawString enforces that its argument is a primitive JSON string or digit sequence"
10:29
<bakkot>
which I think it should, separately from this concern
10:29
<bakkot>
(otherwise you can abuse it to add whitespace, which seems bad)
10:29
<rbuckton>
That's fair, but you're still requiring a fairly complex check that could be more easily exposed by the language.
10:29
<bakkot>
yeah
10:30
<bakkot>
like I said, not saying my proposal is great, just that it's possible-ish
10:30
<bakkot>
though I guess you do also need to check for boxed numbers / strings, which is also annoying
10:30
<bakkot>
so yeah it seems reasonable to add isRawString
10:31
<rbuckton>
An isRawString shim is much more complex than an isArray shim, which means its much easier to get wrong if you're rolling your own serializer (which, I would argue, is the exact situation you're in if you're using this functionality).
10:32
<Tierney Cyren>
is there a good way for me to get involved in the LF discussion meetings?
10:32
<ryzokuken>
Tierney Cyren: you may join this ad-hoc group
10:32
<Tierney Cyren>
I've wanted to participate but how is unclear and I've not gotten around to asking
10:32
<ryzokuken>
write an email to Patrick and he'll add you to the mailing list/event
10:33
<ryzokuken>
talk to Isabelle, I think she's responsible for getting everyone interested from MS involved
10:46
<Tierney Cyren>
$4m seems like a lot but what's the actual runway that provides?
10:46
<Tierney Cyren>
that number does not feel helpful in a vacuum
11:59
<Rob Palmer>
we are starting up plenary in 1 minute!
12:06
<devsnek>
it also happened with NativeError.prototype.toString lol
12:06
<devsnek>
chrome had that from 2016 to 2019 until i removed it
12:09
<Michael Ficarra>
instead of tightening host restrictions, can't we just make a section of test262 which is like "okay to fail, but should probably be looked at because it's more likely a bug"?
12:10
<devsnek>
i was just thinking that
12:10
<devsnek>
like a common-sense directory lol
12:10
<shu>
Michael Ficarra: yes, i plan to bring that up
12:10
<shu>
i object to this proposal
12:10
<Michael Ficarra>
thanks shu
12:10
<devsnek>
what is this proposal
12:10
<shu>
but i'll wait for jordan to actually say the proposal first
12:10
<ljharb>
i haven't made it yet.
12:10
<shu>
before i add myself to the queue
12:11
<devsnek>
i assume its restricting names on prototypes or smth
12:11
<Michael Ficarra>
test262-parser-tests has a "fail" directory which is spiritually the same: https://github.com/tc39/test262-parser-tests/tree/master/fail
12:12
<ljharb>
"Any property on a given object mentioned in the specification, must ONLY appear in the locations specified on that object or its prototype chain"
12:12
<devsnek>
test262-parser-tests has a "fail" directory which is spiritually the same: https://github.com/tc39/test262-parser-tests/tree/master/fail
good filenames
12:13
<Michael Ficarra>
hey you try naming files with random non-programs
12:13
<Michael Ficarra>
pull requests welcome
12:14
<ryzokuken>
are the names just a hash of the source code or sth?
12:14
<Michael Ficarra>
yep ryzokuken
12:14
<devsnek>
i like test262 names
12:17
<bakkot>
man I gotta update that project
12:17
<ryzokuken>
test-assignment-really-terribly-bad.js
12:19
<bterlson>
would we need work in e.g. test262.report if we add a new test category?
12:21
<devsnek>
test262.report is bocoup right
12:21
<bterlson>
Yeah
12:23
<bterlson>
I wonder if there are test cases we would like to add to 262 aside from things we feel bold enough to tighten that would nonetheless be useful for impls? I seem to recall tests in this category back in the day but have forgotten
12:23
<devsnek>
gc tests :P
12:26
<bterlson>
devsnek: you ready to present extending null next?
12:26
<devsnek>
yep
12:26
<devsnek>
assuming i haven't fallen asleep by then
12:27
<bterlson>
do not fall asleep 😁
12:27
<Mathieu Hofman>
gc tests :P
Well for what it's worth, all those are busted right now (relies on removed cleanupSome). I have on my todo list of fixing all these
12:28
<devsnek>
i'm still sad about cleanupsome being separated
12:28
<bterlson>
Justin Ridgewell: if we need to pull in items today, are you available to present destructure private fields?
12:38
<erights>
NativeError.prototype.toString on v8 only shadows Error.prototype.toString uselessly
12:51
<yulia>
hm i didn't really have a position
13:02
<yulia>
lol ok i am going to be the only one maybe
13:05
<rbuckton>
Could super() just be a no-op in class extends null?
13:05
<bakkot>
yulia: someone brought up the possibility of class extends void {} for a statically-analyzable version of null
13:06
<shu>
she brought that up, yeah
13:06
<bakkot>
oh yeah
13:06
<shu>
i guess some SM engineers thought it odd because (void 0) is undefined?
13:06
<bakkot>
sorry I'm tired
13:06
<yulia>
yeah
13:06
<shu>
that doesn't seem like a very big objection, though
13:06
<shu>
the void 0 thing
13:06
<yulia>
yeah i think it works kind off well personally
13:06
<ljharb>
it's super weird to not allow extends X for any valid X, and to force null to have a special syntactic form
13:07
<ljharb>
null objects aren't supposed to be special, from a conceptual standpoint
13:07
<bakkot>
null was already special
13:07
<bakkot>
you have to extend a constructor
13:07
<bakkot>
there is nothing weird about null not working
13:07
<yulia>
i would consider null special
13:07
<bakkot>
it was always going to be special-cased
13:07
<rbuckton>
Besides, you can't write class extends void 0 since a void expression isn't a LHS expression
13:07
<devsnek>
the weird thing is that we special cased it to not throw on definition
13:08
<yulia>
if we can do this from a syntax perspective, i don't mind if super == this
13:08
<yulia>
thats fine
13:08
<rbuckton>
We could always have a shared %NullConstructor% that you assign as the base for class extends null so that super() could work.
13:11
<rbuckton>
i.e., change 8.f.ii. of ClassDefinitionEvaluation to be Let constructorParent be %NullConstructor%., where %NullConstructor% is a built-in function purely for the purpose of supporting super().
13:11
<shu>
yulia: could you expand a bit on the runtime problem in SM?
13:12
<yulia>
basically, we won't be able to determine which bytecode we need to use until we execute it
13:12
<shu>
bytecode for what operation?
13:12
<yulia>
for the look up of the prototype iirc
13:13
<yulia>
im trying to find the notes just a sec
13:13
<shu>
thank you
13:13
<yulia>
"In this proposal if the base is null, the spec is asking for the constructor to still extend Function.prototype (which is reasonable enough). Normally we pull the super target function off the constructors own prototype while running, but in this case we also need to check elsewhere to find the original heritage to know if it was null vs explicitly Function.prototype. This can be fixed for us by adding the original heritage (or at least a flag) to the JSFunction of the constructor. Problem is we don't really have a lot of spare slots or flags on functions themselves"
13:14
<ljharb>
Mathieu Hofman: it's not a base class unless it inherits from Object.
13:14
<ljharb>
perhaps base classes should all have extended null, and extends Object should have been required to make something inherit from Object.prototype, but that ship's long gone
13:14
<shu>
ooh, i see
13:14
<shu>
that makes sense, yes, that's a bigger problem, in its requiring two bits of info
13:17
<ljharb>
(totally fine with me if the constructor itself also inherits from null)
13:17
<bakkot>
the custructor should not inherit from null
13:17
<rbuckton>
(totally fine with me if the constructor itself also inherits from null)
This is problematic if you want to use .bind
13:17
<bakkot>
that makes the function prototype methods not available
13:18
<shu>
yes gut reaction for not having F.p methods on the constructor is "oof" from me
13:18
<ljharb>
Function.prototype.bind.call etc, which is already what robust code does
13:18
<devsnek>
do people use function prototype methods on constructors?
13:18
<bakkot>
ljharb: no code does that
13:18
<ljharb>
lol most doesn't, sure
13:18
<bakkot>
your code, my code, a couple of polyfills, and no one else in the entire world
13:18
<ljharb>
but how often are you binding constructors either
13:18
<bakkot>
not never!
13:19
<shu>
⊤ class
13:19
<bakkot>
that's Object
13:19
<bakkot>
or, well
13:19
<shu>
a second top class
13:19
<bakkot>
i guess we don't really have a lattice
13:20
<devsnek>
TIL bound functions proxy [[Construct]]
13:20
<shu>
create your own!
13:21
<ljharb>
yulia: if the word extends appears then it's not a base class
13:22
<devsnek>
haha shu i was just thinking about structs
13:22
<ljharb>
a base class is class {} which includes inheriting from Object
13:22
<shu>
that is not the usual OO understanding of "base class"
13:22
<devsnek>
yulia: if the word extends appears then it's not a base class
with this pr that's not true
13:22
<ljharb>
i don't care what the spec says about what's base/derived, i'm concerned with what users think
13:23
<yulia>
i feel like this isn't a useful direction for the discussion? there are many ways to read it and virtual classes are really useful
13:23
<ljharb>
and the syntax determines that imo, not theoretical inheritance models
13:23
<yulia>
so, i am happy to consider other syntax
13:23
<yulia>
like, very much -- we don't have to have extends at all
13:23
<shu>
and the syntax determines that imo, not theoretical inheritance models
fairly strong disagree, actually, class hiearchies are very conceptual
13:24
<ljharb>
if that were broadly true then there would have been outcry that default class inheritance wasn't null
13:24
<ljharb>
Object.prototype isn't a "top class" because you can have things that don't inherit from it
13:24
<rbuckton>
a base class is defined in relation to a class definition. In class B extends A {}, A is a base class in that B derives from A. B could always be the base class for another definition (i.e., class C extends B {}).
13:25
<shu>
i meant more like "top of a lattice"
13:25
<rbuckton>
You can say that B is a subclass, and that class A {} is not a subclass
13:25
<shu>
ron's right on general use of "base class", ime
13:25
<ljharb>
i suspect i'm not familiar with that term in the way you're using it (lattice)
13:25
<bakkot>
strong agree with shu here
13:26
<Mathieu Hofman>
a base class is defined in relation to a class definition. In class B extends A {}, A is a base class in that B derives from A. B could always be the base class for another definition (i.e., class C extends B {}).
I probably should have said "root class"
13:26
<jschoi>
🌱 class {}
13:26
<jschoi>
[joke]
13:27
<yulia>
I am going to drop in 3 min, and caroline will take over for mozilla
13:27
<yulia>
so, any further questions on the null stuff, just dm me and ill get to it when im back
13:30
<shu>
ljharb: "root class" is just as cromulent for what i mean, the thing hierarchies top out at
13:31
<shu>
like, surely it's not just syntax, because we set up class hierarchies before class syntax was added
13:31
<shu>
and people understood them just fine
13:33
<ljharb>
sure but in those cases null and a constructor were 100% interchangeable
13:33
<ljharb>
you did have to either omit or conditionally call a superclass constructor, to be fair, but there wasn't a syntactic requirement around super there
13:38
<bterlson>
rbuckton: if we need will you be able to bring forward a regexp item to today?
13:39
<devsnek>
Uint8Array.fromAsync(reader) 👀
13:39
<Mathieu Hofman>
I personally do not think it's weird to have 2 ways to express syntactically a root class: class {} or class extends void {}. In both cases no super() would be allowed
13:40
<ljharb>
a root class doesn't extend anything.
13:40
<ljharb>
root class {} or something would at least not have that confusion
13:40
<rbuckton>
bterlson: That would be fine
13:40
<Mathieu Hofman>
My understanding is that in english void is nothing ;)
13:41
<ljharb>
void produces undefined which is a reified thing, not the absence of a thing
13:41
<devsnek>
technically in english void and null are synonyms
13:41
<Mathieu Hofman>
no, void expr produces undefined
13:41
<bakkot>
class {} does not literally "not extend anything" either - it extends Object.prototype
13:41
<ljharb>
right, currently that's the only way JS supports using void
13:41
<bakkot>
but, the way you think about it is that it does not extend anything
13:41
<ljharb>
bakkot: yes, that's true as well. but it's not explicitly claiming to not extend
13:44
<bakkot>

my point is that "void produces undefined which is a reified thing, not the absence of a thing" is true in a technical sense but it is also true in a technical sense that class {} is a base class but also extends Object.prototype, if you are being that kind of precise.

on the other hand, colloquially it is understood that class {} is a base class, and I argue that colloquially it would be easily understood that extends void means "does not extend anything"

13:44
<devsnek>
i would do null class Foo {}
13:45
<ljharb>
const o = null { a: b } when
13:45
<shu>
there's already proto syntax tho
13:45
<devsnek>
wait hold on
13:46
<devsnek>
class Foo { __proto__: null }
13:46
<devsnek>
what does that do
13:46
<ljharb>
with the colon, it's a syntax error, in TS i'm not sure
13:46
<devsnek>
oh wait it uses DefineOwnProperty
13:46
<rbuckton>
Its a SyntaxError :)
13:46
<ljharb>
__proto__ = null would probably create a public class field, or throw, not sure
13:46
<bakkot>
can someone put in a conclusion for the previous agenda item in the notes
13:46
<devsnek>
man i really wish class fields used Set
13:46
<rbuckton>
In TS its says you have a field named __proto__ whose type is null.
13:47
<bakkot>
I am so glad class fields do not use Set
13:47
<ljharb>
(re conclusion) there really isn't one i think, it's an update
13:47
<shu>
galaxy brain is: what if object literals used set
13:47
<bakkot>
they used to
13:47
<devsnek>
lol
13:47
<devsnek>
ok so we just need to special case
13:47
<devsnek>
__proto__ = null in classes
13:48
<bakkot>
The production
PropertyNameAndValueList : PropertyName : AssignmentExpression is evaluated as follows:
1. Create a new object as if by the expression new Object().
2. Evaluate PropertyName.
3. Evaluate AssignmentExpression.
4. Call GetValue(Result(3)).
5. Call the [[Put]] method of Result(1) with arguments Result(2) and Result(4).
6. Return Result(1).

~ ES3

13:48
<devsnek>
is that SSA
13:49
<rbuckton>
__proto__ = null in classes

Except that already has a well-defined behavior:

> class C { __proto__ = null; }
undefined
> var o = new C();
undefined
> o
C { ['__proto__']: null }
13:49
<shu>
bakkot: wtf
13:49
<devsnek>
ya ik :(
13:50
<rbuckton>

Just need decorators:

@extendsNull
class C {}
13:50
<bakkot>
shu: yeah it was a wild time
13:50
<Michael Ficarra>
rbuckton: doesn't matter if nobody relies on it
13:50
<devsnek>
i need to convert more people to my function decorator cult
13:50
<Michael Ficarra>
devsnek: I'm always willing to try a new cult
13:51
<rbuckton>
i need to convert more people to my function decorator cult
But the hoisting problem though
13:51
<devsnek>
that kind of hoisting is almost never used in practice
13:51
<devsnek>
i don't know a case where its needed
13:52
<rbuckton>
Function expression decorators: ✔️ Function declaration decorators: ☠️
13:52
<devsnek>
i seriously believe we can do decorators on function declarations by just not hoisting the function value
13:52
<rbuckton>
that kind of hoisting is almost never used in practice
That kind of hoisting is about 80% of the TypeScript compiler
13:52
<devsnek>
no one will notice
13:52
<bakkot>
i definitely hoist a lot of functions
13:52
<bakkot>
on the other hand, decorators are bad, so I'm ok with them being harder to use :P
13:52
<devsnek>
but do you call them before their declaration
13:52
<devsnek>
or just refer to them
13:52
<bakkot>
devsnek: yes
13:53
<bakkot>
call them
13:53
<devsnek>
ok but
13:53
<devsnek>
you can just
13:53
<devsnek>
not put decorators on that code
13:53
<bakkot>
functions go at the bottom, logic goes at the top
13:53
<bakkot>
(for scripts)
13:53
<devsnek>
its a simple choice
13:53
<rbuckton>

At one point I was thinking about this:

// not hoisted
// not reassignable
const function f() {} 

// ok
@dec
const function f() {}

// syntax error
@dec
function f() {}
13:54
<rbuckton>
as a shorthand for const f = function () {}
13:54
<devsnek>
ron i won't accept stylistic practice from the typescript compiler source
13:54
<bakkot>
const function means a different thing
13:54
<devsnek>
until you get rid of the 40k line file
13:54
<rbuckton>
const function means a different thing
in some languages, yes.
13:55
<rbuckton>
Either way, I'm all for function expression decorators.
13:55
<devsnek>
anyway i think this would work in practice and i encourage some company that has resources to do research to research it
13:55
<rbuckton>
Just not a fan of adding a decorator breaking hoisting on a function declaration.
13:56
<devsnek>
i don't think it counts as "breaking" as much as a tradeoff, it certainly doesn't have any impact on existing code
13:56
<rbuckton>
since it also can break circular imports
13:56
<devsnek>
it can't break circular imports
13:56
<bakkot>
re: the actual presentation, I strongly agree with WH
13:56
<bakkot>
sqrt is good, we should have it
13:56
<devsnek>
i mean it can break stuff but that's unrelated to the circular importing
13:57
<rbuckton>
Its the same hoisting problem. Hoisted functions are reachable in a circular import.
13:58
<devsnek>
right i'm just saying the base case is unchanged and the worst case is the same case as an undecorated class or a variable decoaration
13:58
<rbuckton>
i don't think it counts as "breaking" as much as a tradeoff, it certainly doesn't have any impact on existing code
The problem with existing code is that existing code is some the code I want to decorate, and adding a decorator would change the semantics in a non-obvious way.
13:58
<devsnek>
its the same hazard as migrating function to class
13:59
<rbuckton>
There's a hidden hazard with decorators that doesn't exist when you're changing function -> class, in that its still function.
14:00
<ljharb>
adding a decorator to something definitely risks all sorts of breakage; it's not something you can expect to do transparently
14:00
<rbuckton>
If someone is using TypeScript (or JS with the TS language service), we could give you errors on decorated function decls.
14:00
<ljharb>
you could do that when it breaks hoisting too
14:01
<ljharb>
(not arguing for breaking hoisting, necessarily, but none of those are compelling arguments against doing so to me)
14:01
<devsnek>
p sure the two most popular eslint configs yell at you about hoisting and circulars, separately
14:02
<devsnek>
anyway that's my hill
14:03
<devsnek>
honestly i don't even know what i would use decorators on classes for lol
14:03
<devsnek>
and yes i know there are use cases, that's just where i'm at personally
14:03
<ljharb>

i very much want

@bound
method () {}

to be a thing

14:03
<rbuckton>
honestly i don't even know what i would use decorators on classes for lol
I use them in TS for so many things.
14:04
<rbuckton>
I want function decorators, but I also want parameter decorators.
14:04
<devsnek>
i just want @router.get('/foo') function getFoo() {}
14:04
<ljharb>
why that over router.get('/foo')(function getFoo() {})?
14:05
<devsnek>
because that's ugly and weird
14:05
<devsnek>
and it doesn't compose well
14:06
<devsnek>
@get('/foo') @loginRequired({ admin: true }) @ratelimit(4, 1) function getFoo() {}
14:06
<ljharb>
|>?
14:06
<rbuckton>

One reason:

const getFoo = router.get('/foo')(() => {});
getFoo.name; // ""

// vs
const getFoo = @router.get('/foo') () => {}
getFoo.name; // "getFoo"
14:06
<devsnek>
lol
14:06
<ljharb>
lol fair, but that's not compelling to me; if you want a name don't use arrows
14:06
<devsnek>
i do not like expression position decorators lol
14:06
<ljharb>
i will always believe FNI was a mistake
14:07
<rbuckton>
ljharb: You want @bound, let the snek have @dec function f() {}.
14:07
<Michael Ficarra>
FNI was a mistake only if you think name is anything other than a debugging facility
14:07
<ljharb>
lol
14:08
<ljharb>
FNI was a mistake only if you think name is anything other than a debugging facility
i think it's a mistake BECAUSE it's a debugging facility. inferred names are harder to grep for.
14:08
<bakkot>

i just want @router.get('/foo') function getFoo() {}

oh god, this is the strongest argument against function decorators I've yet seen

14:08
<devsnek>
just have a code server that supports fni
14:08
<bakkot>
if people are going to write code like that we should not have function decorators
14:09
<devsnek>
like what
14:09
<rbuckton>
The VS Code codebase uses so many parameter decorators for DI, we really need parameter decorators shortly after class/member decorators.
14:09
<devsnek>
oh
14:09
<devsnek>
bakkot: have you never used flask in python
14:09
<bakkot>
devsnek: yeah, it's awful
14:09
<devsnek>
its incredible
14:09
<devsnek>
lol
14:09
<rbuckton>
Parameter decorators are so much higher on my list of priorities than function decorators, tbh.
14:09
<bakkot>
especially debugging other people's flask apps
14:09
<bakkot>
just instantly unmaintaintable nightmares
14:10
<devsnek>
idk i love it
14:10
<devsnek>
aside from the globals
14:10
<rbuckton>

Worst case, you can do:

const { getFoo, ... } = class {
  @router.get("/foo")
  static getFoo() {}
};
14:10
<devsnek>
should've been function parameters
14:15
<shu>
at a deep level i really do not get dependency injection
14:17
<devsnek>
i do not like DI
14:29
<shu>
but what is it?
14:29
<shu>
is it just passing parameters or is there some gestalten design thing i'm not getting
14:30
<jschoi>
Yeah. It’s just passing parameters.
14:32
<devsnek>
but then you need parameter decorators for some reason
14:34
<jschoi>
It’s kind of like the opposite of import in that…if A needs B and B needs C—instead of B pulling in specifically C for itself—B defines a parameter, A pulls in B and C, and A plugs C into B’s parameter.
14:35
<jschoi>
So A can switch C with something else compatible with B’s parameter before plugging it into it.
14:35
<rbuckton>
most DI is something like taking parameters for your dependencies, but the actual construction of your object graph is handled by a composer.
14:36
<rbuckton>
It allows you to substitute dependencies for testing or even at runtime based on circumstance.
14:38
<jschoi>
Whenever we need to refer to another variable elsewhere, we always face the choice of (1) defining it as a function parameter to be plugged in later or (2) importing it and hardcoding the dependency. There’s nothing special about that choice; it’s just an everyday thing.
14:40
<jschoi>
Although like Ron said, some people use elaborate composer systems that try to abstract away this plugging in for you.
14:40
<rbuckton>

DI using parameter decorators is something like:

// service-c.js
class ServiceC {
  constructor(
    @inject("service-a") serviceA,
    @inject("service-b") serviceB
  ) { ... }
}

// main.js
import { ServiceA } from "./service-a.js";
import { ServiceB } from "./service-b.js";
import { ServiceC } from "./service-c.js";

const container = new ServiceContainer();
container.addClass("service-a", ServiceA);
container.addClass("service-b", ServiceB);
container.addClass("service-c", ServiceC);

const instance = container.getService("service-c"); // gets or creates a ServiceC from the container
14:41
<rbuckton>

And in test code, you can do:

import { ServiceC } from "./service-c.js";
it("test the service", () => {
  const mockServiceA = { ... };
  const mockServiceB = { ... };
  const serviceC = new ServiceC(mockServiceA, mockServiceB);
  // test serviceC using mock behavior from dependencies
});
14:43
<rbuckton>
Of course, main.js is a contrived example. Many DI containers are fairly flexible, allowing you to specify optional dependencies, aggregate dependencies, nested containers, disposable containers (to manage component lifetime), etc.
14:43
<rbuckton>
As well as lazily-injected circular dependencies using field decorators.
14:54
<shu>
and why would i want to do this?
14:56
<rbuckton>
You would want to do this if you're a very large application that wants to break itself into smaller components for ease of maintenance, or support pluggability/extensions.
14:58
<rbuckton>
Its a fairly common idiom across any number of programming languages. Its primarily used server-side or in desktop applications, though not so much in the browser.
14:58
<shu>
the easiest to maintain thing is the thing i can read
15:00
<rbuckton>
Like anything, once you understand the system its fairly easy to read.
15:08
<rbuckton>

The VS Code system has a decorator factory that is used to create the actual decorators used for injection, and names the decorators to coincide with the interface name of the expected input. As a result, your code looks something like (in TS):

// themeService.ts

// describe the service interface
export interface IThemeService {
  getColorTheme(): IColorTheme;
  readonly onDidColorThemeChange: Event<IColorTheme>;
  getFileIconTheme(): IFileIconTheme;
  readonly onDidFileIconThemeChange: Event<IFileIconTheme>;
}

// create a decorator for the service
export const IThemeService = createDecorator<IThemeService>();

// editor.ts
import { IThemeService } from "./themeService";

export class EditorService {
  themeService: IThemeService;

  constructor(
    @IThemeService themeService: IThemeService
  ) {
    this.themeService = themeService;
  }

  // code that uses themService
}
15:10
<rbuckton>
All the decorator does is provide the glue that the composition plumbing uses. The idea is fairly similar to context providers in React. I don't have to dig around in the plumbing of my application to find and use a dependency, I merely need to declare that I require the dependency and the composition runtime will either provide it, or fail to compose early because of an unsatisfied dependency.
15:11
<rbuckton>
Its also much like a module import graph, except that you have more control over lifetime of the entire dependency graph.
15:12
<rbuckton>
The Managed Extensibility Framework (MEF) API in .NET is another example of a DI system, and it actually uses Import and Export attributes to define dependency relationships within a DI container.
15:17
<sarahghp>
this seems like an invitation to write unnecessarily complex code ... the virtue of low-magic is links are really clear (I also don't really like context in React tho)
15:18
<sarahghp>
to be verbosely clever instead of code-golf-style tersely clever
16:01
<rickbutton>
does anyone have a good resource for descriptions of non-standard JS extensions over the years? as-in, pre-harmony stuff that got removed like conditional catch / let blocks / etc
16:03
<rickbutton>
rubber duck method, i guess this is a good resource: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features
16:07
<annevk>
Doesn't list E4X!
16:07
<jschoi>
Microsoft JScript had a special :: syntax for events, I believe. (CC: rbuckton)
16:08
<Jack Works>
And JScript supports `expr() = expr2`?
16:10
<rickbutton>
what does that do?
16:11
<jschoi>
Golly, I do not remember. I wish there were still resources about this. 😔
16:12
<Jack Works>
what does that do?
IDK, just heard of that
16:13
<nicolo-ribaudo>
what does that do?
I think it was to allow things like `eval("x") = 2`, but I might be wrong
16:15
<rickbutton>
wild
16:16
<rickbutton>
eval("x") = 2 is what engine developers fear
16:23
<nicolo-ribaudo>
Ok, maybe it wasn't for eval: the ES 1 specification, at paragraph 11.2.3, says that any host function can return a reference, but `eval` doesn't
16:26
<rbuckton>
You can do (x) = 2 today, but you can't do ({ x }) = { x: 2 }.
16:28
<rbuckton>
not a non-standard extension, just an idiosyncrasy of the language
16:29
<nicolo-ribaudo>
We are taking about `fn(x)=2`, not `(x)=2`!
16:48
<ljharb>
can anyone read through https://tc39.es/ecma262/#sec-number.prototype.toexponential and confirm whether 0.0.toExponential(2) should be '0.00e+0' vs '0e+0'?
17:05
<shu>
uh, looks like 0.00e+0
17:06
<shu>
f is 2, so step 9.a says m is "000"
17:06
<shu>
then step 11 puts a . after the first digit
17:14
<ljharb>
ok cool, thanks - that seemed right to me but i wanted another check
17:51
<bakkot>
re: f() = x, see discussion in https://github.com/tc39/ecma262/issues/257
18:01
<bakkot>
on some previous occasion this came up, bterlson said it was vbarrays specifically that used this
18:02
<bterlson>
yessir, vbarrays
18:02
<bakkot>

and someone in the meeting chat said:

[2:21 PM] Paul Leathers
Yes, VB arrays are the case I remember.

[2:23 PM] Paul Leathers
The call didn’t really return an lvalue. If the engine saw a(i) = x, it would perform a call to IDispatch::Invoke with the PROPERTY_PUT attribute set and the value to write as an extra argument.
18:02
<bakkot>
though I never did manage to find an example of this I could actually get to work in IE6
18:02
<bakkot>
maybe that was too new though
18:03
<bterlson>
I think because of the COM magic, it's possible vbs wouldn't work right on newer systems even if IE6 and JScript+JS did
18:03
<bakkot>
oh, fun
18:03
<bterlson>
I recall there being some particular system setup when I was testing VB/JS interop
23:57
<ptomato>
hi chairs, re. https://github.com/tc39/Reflector/issues/396#issuecomment-952406138, chipmorningstar needs a power-up 😄