| 17:59 | <rbuckton> | My apologies, I will be about 2 minutes late to the working session today |
| 19:01 | <rbuckton> | shu: At one point you had discussed having one shared struct inherit from another shared struct. If we ignore TLS prototypes and behavior for a moment, is there any specific benefit to modeling an actual inheritance model here, or would having the inherited struct just maintain the initial field layout of the base struct be sufficient? |
| 19:03 | <shu> | i think the benefit is more like "full composability with rest of the language", mainly |
| 19:03 | <shu> | i know the field has kind of soured on inheritance hierarchies vs inline storage of stuff |
| 19:03 | <shu> | but for e.g. AST nodes, you probably do want an inheritance hierarchy in the "layout prefix" sense that i was imagining |
| 19:04 | <shu> | AstNodeBase has loc or whatever |
| 19:04 | <rbuckton> | I'm more asking if there is any reason that struct B extends A {} needs to care about A other than its field layout (if you ignore TLS prototypes and constructor initialization logic) |
| 19:05 | <rbuckton> | (aside from internal AST reasons) |
| 19:05 | <shu> | ooh |
| 19:05 | <rbuckton> | It goes to simplifying the syntax I've been considering. |
| 19:05 | <shu> | i feel like no? |
| 19:05 | <shu> | my intention was literally for layout |
| 19:06 | <rbuckton> | In classes, field order is determined by calling super(), where each super constructor installs its fields and returns the thing to be the used as the this in the subclass constructor. |
| 19:06 | <rbuckton> | That helps |
| 19:08 | <shu> | what i want for struct inheritance semantics:
|
| 19:08 | <shu> | the invariant is that a half-constructed, out-of-declared-order instance is not observable if you use structs |
| 19:14 | <rbuckton> | That syntax sketch I wrote up a few months back has a lot of corner cases to handle future complexity, like:
I'd like to cut a lot of that for simplicity's sake. For example, every |
| 19:14 | <rbuckton> | So shared struct A extends B {} gives A a TLS prototype that inherits from B's TLS prototype. |
| 19:15 | <rbuckton> | If you do shared struct A extends B {} and B isn't shared, it doesn't matter. You just get A with the same layout as B, except it's shared, and the prototypes are non-shared anyways. |
| 19:17 | <rbuckton> | In a struct constructor, super() could be designed such that it doesn't support return override tricks, since the layout is already wired up. |
| 19:19 | <rbuckton> | And we could just assume methods are non-shared by default, and if shared functions ever becomes a thing you have to opt-in on a method-by-method basis. That seems like a good idea anyways, since you'd want to explicitly indicate that you'd thought about thread safety for a given "shared" method anyways. |
| 19:19 | <rbuckton> | All of that makes the syntax fairly simple. |
| 19:25 | <rbuckton> | Basically:
|
| 19:27 | <rbuckton> | Ideally, we could find some way of supporting private names and accessor, as I'd also like to support decorators long term. The private names bit is tricky for shared structs, though, as you wouldn't be able to guarantee "hard privacy" if it were supported, but private names are necessary to support accessor for decorators. |
| 19:30 | <Mathieu Hofman> |
Couldn't private declarations help? |
| 19:31 | <rbuckton> | IMO, private names should be viable and are just part of the field layout. Wiring up identical struct definitions between two workers would verify they have identical layouts. It might not be true "hard privacy" though, if you are able to create a new worker with an altered struct definition that can still be correlated, but has a prototype method that exposes the private field. Maybe its not actually an issue, though, if we are planning to have struct layout identity based on file path/line number/etc. |
| 19:31 | <rbuckton> | Not unless private declarations are also shareable, and that seems even less safe. |
| 19:32 | <Mathieu Hofman> |
I suspect if you had to explicitly register your structs, you could guarantee true privacy for private fields ;) |
| 19:33 | <rbuckton> | If the correlation mechanism is still file+position based, as we've discussed previously, then hard privacy isn't as much of an issue because the declarations have the same code. |
| 19:33 | <Mathieu Hofman> | Correct |
| 19:33 | <rbuckton> | If you had to use an API to explicitly register, you have even less privacy. |
| 19:33 | <Mathieu Hofman> | it's only a problem if you can forge the struct definition |
| 19:34 | <rbuckton> | Since I could spin up a Worker that registers its own version of the class that just replaces its methods with return this.#whatever and programmatically wire them up. |
| 19:34 | <rbuckton> | To prevent forging the struct definition, it would likely need to be path+position based |
| 19:34 | <Mathieu Hofman> | If you had to use an API to explicitly register, you have even less privacy. |
| 19:35 | <rbuckton> | If I have access to construct a Worker to do the right thing, then I have access to construct a Worker to do the wrong thing. |
| 19:35 | <rbuckton> | Unless that Worker has no control over how the correlation happens. |
| 19:36 | <Mathieu Hofman> | instead of using examplar |
| 19:36 | <rbuckton> | If I can send a trusted piece of information over to a Worker to establish the struct, then malfeasant code can do the same thing to forge the struct as well. |
| 19:37 | <Mathieu Hofman> | not if that piece of information is only obtained when declaring the struct |
| 19:38 | <rbuckton> | How do you do that, and have it declared in two different threads with the same information? |
| 19:39 | <Mathieu Hofman> | that's the tricky bit, especially with syntax |
| 19:39 | <rbuckton> | file+position is essentially obtained when declaring the struct and is potentially unforgeable (especially if all workers pointing to the same file have to use the same cached source) |
| 19:40 | <Mathieu Hofman> | I can do it imperatively. I believe I actually did in some of my earlier attempts at linking types |
| 19:41 | <rbuckton> | Also, my argument isn't that "if we can't do hard privacy we can't have this feature", it's "if we can't do hard privacy, users would need to accept that if they want to use this feature" |
| 19:42 | <Mathieu Hofman> | I agree that file + position is unforgeable (caveats when you start introducing a module loader). I was talking about an escape hatch to avoid that constraint |
| 19:43 | <rbuckton> | To be fair, the forgeability is only a concern if you hand untrusted code the ability to create a new Worker with the necessary correlation information. If the untrusted code doesn't have access to that, they can't forge it. |
| 19:46 | <rbuckton> | file+position is potentially easier for consumers as its less complex to set up, though its harder for bundlers since they need to isolate struct definitions to individual files. Defining some kind of private token that you need to attach to a declaration before the module graph is loaded seems extremely hard to do correctly, and if the token is just a string/URI/UUID then malfeasant code just needs to know what that string is to construct a new Worker that points to a different file with a struct that masquerades as the original one. |
| 19:47 | <rbuckton> | I'm a fan of being able to tag a struct with something like a UUID to correlate, but it does weaken private names in that context. |
| 19:48 | <rbuckton> | Of course, malfeasant code would have to be able to execute a custom tailored script, which could run afoul of CSP in a properly configured environment, so maybe that's not so much of a concern either. |
| 20:38 | <rbuckton> | shu: Could you make me a maintainer on https://github.com/tc39/proposal-structs? I don't seem to have enough access to add PR reviewers |
| 21:32 | <shu> | rbuckton: done |