2022-12-01 [01:00:41.0942] what is the last item scheduled for today? [01:03:59.0268] ShadowRealm [01:04:20.0889] but it may move slightly if time magically opens up [01:05:19.0833] i should like to be present for shadowrealms but tbqh i don't know if i'll be able to stay awake until then [01:08:43.0504] * can someone let me in to the meet? Rob Palmer ryzokuken bterlson [01:09:42.0465] bakkot: can you add `amazing iterator` -> `async iterator`? [01:19:01.0217] bterlson: advance queue? [01:23:17.0533] As Michael Saboff said, please add your name, abbreviation and organization to today's meeting notes. Link here: https://github.com/tc39/Reflector/issues/446 [01:24:41.0614] Please could everyone avoid sharing direct URLs to our notes or meeting URLs in this publicly logged room. These are all linked from the Reflector post so that they are kept private. [01:28:35.0651] already 15 min ahead! [01:37:47.0951] Wouldn't this be also the first time, that we would have a ~_keyword_~operator being extended / giving a `.` ? `await` is not a normal function but sth. _magical_ like `delete`? [01:37:58.0172] * Wouldn't this be also the first time, that we would have a ~_keyword_~operator being extended / giving a `.` ? `await` is not a normal function but sth. _magical_ like `delete`? [01:38:14.0029] no, we have `new.target` [01:38:16.0649] and `super.whatever` [01:38:24.0849] and `import.meta` [01:38:29.0365] metaproperties are already A Thing [01:40:01.0298] `super.whatever` is used like a magical function and `new.target` is a magical property. `import.meta` a magical global. [01:40:21.0431] speaking of research -- anyone wanna take over running that call? [01:42:11.0843] I can relate to the idea of hiding Promises, but it would come at the cost of having - IMO - at the cost of another magical thing, that we do not have so far. [01:43:37.0048] * `super.whatever` is used like a _magical_ **function** and `new.target` is a _magical_ **property**. `import.meta` a _magical_ **global**. [01:44:40.0090] it could be an `awaitAll` keyword and it'd be the same [01:44:44.0490] we have issues with sound in the room? [01:44:58.0460] `await.all` isn't magical, it's just a way to nest keywords under keyords in this case [01:44:58.0678] `await.all [a, b, c]` just doesn't seem that different from `await Promise.all([a, b, c])` to me [01:45:00.0480] async/await isn't about hiding promises. It's about taking continuation passing style code and allowing you to write it as linear code so that you can maintain the benefit of existing control flow constructs like `continue`/`break`/`return`/`yield`. [01:45:20.0890] what is being highlighted right now is the issue of breaking consistency in a language -- new developers will have a hard time keeping two things in mind [01:45:30.0278] > <@bakkot:matrix.org> `await.all [a, b, c]` just doesn't seem that different from `await Promise.all([a, b, c])` to me sure, i do the latter all the time, but in my experience devs in fact consider those quite different, and avoid the latter in favor of `await a; await b; await c` *constantly* [01:45:31.0280] I would question teaching async/await before promises for this reason [01:45:45.0867] i don't think the solution here is syntax [01:46:00.0430] In other words, is there so far any `await`-like thing with a `.`? [01:46:18.0765] Christian Ulbrich: no, in that respect it'd be something new, but almost every new syntax feature has that :-) [01:46:30.0645] yulia: i totally agree this doesn't fix the education problem. to me it's fixing an ergonomics problem. [01:46:33.0799] DevMo has a great example of how to do promises in sequence via Array.prototype.reduce [01:46:36.0953] ljharb I definitely see people do `await a; await b; await c` over `await Promise.all` but I have generally assumed it was because of thinking serially rather than because of wanting to avoid `Promise`? [01:46:46.0696] > <@ljharb:matrix.org> yulia: i totally agree this doesn't fix the education problem. to me it's fixing an ergonomics problem. the ergonomics issue is relying on learners to make its poinit [01:46:48.0438] "wanting to avoid `Promise`" seems like a problem we should not be trying to solve for them [01:46:49.0924] you just did so as well [01:46:55.0423] I imagine `for await all` would take a lot of pressure off of `await.all`. [01:46:57.0868] i don't agree that this is a solution [01:47:02.0745] > <@bakkot:matrix.org> ljharb I definitely see people do `await a; await b; await c` over `await Promise.all` but I have generally assumed it was because of thinking serially rather than because of wanting to avoid `Promise`? my experience is that most of the time they don't need to be serial, and that they didn't want to deal with the ceremony of Promise.all [01:47:12.0974] Motivate a change to "unconfuse" developers from Promises by adding a totally new syntax, should be well-thought of. [01:47:13.0824] > <@bakkot:matrix.org> ljharb I definitely see people do `await a; await b; await c` over `await Promise.all` but I have generally assumed it was because of thinking serially rather than because of wanting to avoid `Promise`? this rings true to me [01:48:00.0795] i reckon beginners are not at a place that _would_ reach for parallel combinators but for a more discoverable way to do so, like syntax, or something [01:48:08.0880] they're just not at a place to reason about things in parallel [01:48:21.0279] Concerning the hiding motivation, it actually is a problem that have all the time with junior developers, they already do not know, that they are using promises... [01:48:49.0535] I agree with Yulia that promises should be taught before async/await. I agree with rbuckton that the latter is not to hide the former. [01:49:11.0153] if the main problem is await all, should we only add `await.all` without `await.race` and others? Or even just introduce a special syntax (eg. `await* promises`)? [01:49:15.0358] first we must teach one of the First Mistakes -- the existence of the microtask queue [01:49:22.0678] shu: The whole Playwright API is heavily _async_... [01:49:48.0922] Christian Ulbrich: sorry what's the context of that comment? i don't know what that means [01:50:07.0352] * I agree with @yulia that promises should be taught before async/await. I agree with rbuckton that the latter is not to hide the former. [01:51:06.0460] > <@haxjs:matrix.org> if the main problem is await all, should we only add `await.all` without `await.race` and others? Or even just introduce a special syntax (eg. `await* promises`)? that would still be better than the status quo, but i think that'd be an unfortunate inconsistency [01:51:43.0848] "do nothing" is always an option. [01:51:46.0985] ljharb: if syntax, there is no inconsistency? [01:52:37.0597] the inconsistency would be 3 of the promise combinators lacking syntax, but 1 having it [01:55:21.0091] I mean, if 80% usage of combinators is just Promsie.all, is it ok to only add syntax for it? [01:58:15.0628] should we only add a subset of + - * /, because some of them get more usage than the others? [01:58:45.0317] i remain adamant that consistency is important for learnability of a language, 20% is a pretty big chunk, but i think we are just pulling numbers from the air right now. [02:00:02.0228] > <@yulia:mozilla.org> speaking of research -- anyone wanna take over running that call? Sad to see you cannot attend [02:00:17.0478] > <@jackworks:matrix.org> Sad to see you cannot attend wanna take over? [02:00:38.0941] you can bug felienne [02:00:55.0202] or we can go without a research call for a while [02:10:05.0658] bterlson: it'd be really helpful to plan my sleep if the hackmd could be updated with an estimated plan for the rest of the day 🙏 [02:10:42.0349] > <@shuyuguo:matrix.org> they're just not at a place to reason about things in parallel Sounds true. For beginners they need to be taught to be aware of parallel. But for me, I sometimes drop parallelism because I'm lazy to write Promise.all() [02:11:35.0547] ljharb: hackmd is constantly kept as up to date as we can make it - as we approach the end of the meeting it generally becomes more reliable as uncertainty decreases [02:11:46.0447] I think Promise.all is the tip of the lazy iceberg. To go from lazy about parallelism to rigorous usually means converting a for loop to a map, or accumulating an array of promises in a for loop. [02:11:50.0966] thanks, appreciate whatever you can do! [02:12:32.0391] Jack Works: have you tried to use editor abbreviations? [02:12:36.0005] > <@alex.vincent:matrix.org> "do nothing" is always an option. I'm prepared and think it's ok if the committee dont like this idea [02:12:53.0268] like this seems like an easier problem to solve than through the language [02:13:15.0633] A much bolder improvement would be parallel for await loops. [02:15:12.0807] > <@ljharb:matrix.org> should we only add a subset of + - * /, because some of them get more usage than the others? OOT: imo some operators (| & %) do not deserve a single character operator because they're not used so often (than || &&) [02:15:58.0712] > <@jackworks:matrix.org> OOT: imo some operators (| & %) do not deserve a single character operator because they're not used so often (than || &&) As someone that does a fair amount of bit twiddling, I disagree [02:16:25.0074] > <@jackworks:matrix.org> OOT: imo some operators (| & %) do not deserve a single character operator because they're not used so often (than || &&) K&R’s Regret. They got to bitwise first. [02:16:32.0906] I would like 10 minutes of plenary time to discuss an additional question regarding Intl NumberFormat v3. I have added a slide at the end of the deck I used on Tuesday [02:16:42.0360] > <@yulia:mozilla.org> wanna take over? I have little knowledge about how to do research... I can join the meeting but I don't think I can take the charge 👀 [02:16:53.0843] sffc: have you proposed strings for the eras anywhere? [02:17:06.0777] i think the only person who really knows what they are doing is felienne [02:19:13.0014] i know how to confirm my priors and ignore counter evidence [02:19:32.0216] > <@shuyuguo:matrix.org> Jack Works: have you tried to use editor abbreviations? Oh [02:20:55.0921] One of the main pain point is when I'm using for...of loop and I want to use await in the loop body [02:20:57.0975] > <@shuyuguo:matrix.org> i know how to confirm my priors and ignore counter evidence sounds like ✨science✨ [02:21:05.0970] apaprocki: All context and work on this should be available on the proposal repository; e.g. https://github.com/FrankYFTang/proposal-intl-era-monthcode/issues/1 ... my colleague Manish has a proposal that has been presented to CLDR. There is still room to evolve before they are written down. Would like your feedback if interested! [02:21:09.0575] > <@jackworks:matrix.org> One of the main pain point is when I'm using for...of loop and I want to use await in the loop body Does anyone have the same feeling about this? [02:22:33.0369] slides aren't showing on meet [02:22:36.0497] blank screen [02:22:39.0421] * blank screen [02:22:46.0644] I can't see the slide [02:26:19.0071] * you don't need to understand what membrane is to know the motivation of this proposal [02:26:57.0910] TLDR: we have tons of revocable Proxies and we need to revoke them in an ergonomic way. [02:28:28.0974] I mean you kind of do if you've never encountered proxy revocation before (I merely know that it's a thing that exists that I've not needed to investigate to any depth) [02:30:09.0993] today: ```js for (const p of proxiesToRevoke) revokerMap.get(p)() ``` You need to store tons of revoker functions and call them one by one [02:31:56.0121] I'm curious if `DisposableStack` can help here, either: ```js const { proxy, revoke } = Proxy.revocable(...); stack.defer(revoke); return proxy; // later stack.dispose(); // mass revocation ``` Or make the return value of `Proxy.revocable` itself a disposable. [02:32:59.0252] Cannot help. [02:33:20.0847] Proxy does not have a prototype to store @@disposable [02:33:32.0420] I'm not sure how I see how [02:34:17.0438] the confusing name of `defer` aside, that seems like it'd work [02:34:31.0104] Is the memory argument really so strong? [02:35:39.0872] I don't think we need to determine whether DisposableStack would be sufficient today [02:35:57.0745] this can reach stage 1 and later we don't advance to stage 2 because we realise DisposableStack is a fine solution [02:36:08.0411] /me has never _revoked_ a proxy, why would I? I want all of them to go through **my** _proxy_ :) [02:37:12.0529] > <@jackworks:matrix.org> Proxy does not have a prototype to store @@disposable DisposableStack defer still could work [02:37:49.0649] And I meant that its not the proxy that is disposable, but the `{ proxy, revoke }` object [02:38:30.0478] I think DisposableStack could work, but the proposed api seems much easy to use. [02:39:06.0432] I also had the https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal in mind... [02:40:31.0056] yeah, cross realm seems a requirement of this proposal? [02:41:14.0405] I think wrapped revokers could also be optimized in theory, but would any engine realistically perform all these optimizations? [02:42:20.0804] Proxies now have some heavy uses... they are used by recent Vue3 and libs implementing _immutability_. [02:42:45.0271] I still wonder though, whether they are actually _revoking_ their Proxies... [02:42:59.0966] Not sure whether Vue3/Mobx use revoke... [02:43:01.0605] hard to disagree with shu here as my point above was that I've never actually _used_ a Proxy, let alone thought about revoking one [02:43:38.0829] you may not have used one directly, but lots of libraries / frameworks do leverage them under the hood [02:43:45.0110] (not that I'm objecting to anything :P my point above was that without membranes, there isn't an obvious context of usage) [02:44:16.0925] > <@haxjs:matrix.org> yeah, cross realm seems a requirement of this proposal? don't know maybe we need ask caridy [02:44:24.0280] I would like to see usage trackers from web browsers (which do not cover the usages in non browser environments) [02:44:42.0797] > <@rbuckton:matrix.org> And I meant that its not the proxy that is disposable, but the `{ proxy, revoke }` object oh that works. [02:45:05.0022] Membranes are just a mental model around Proxies. Their are many use cases for Proxies, it does not matter, whether they are actually membranes. [02:45:38.0513] * I would like to see usage numbers from web browsers (which do not cover the usages in non browser environments) [02:46:15.0320] * Membranes are just a mental model around Proxies. There are many use cases for Proxies, it does not matter, whether they are actually membranes. [02:47:49.0097] Vue is proxying objects to implement immutability? [02:47:52.0347] they expect this to be performant? [02:48:21.0351] @shu They use it to implement reactivity. [02:48:40.0205] > <@shuyuguo:matrix.org> Vue is proxying objects to implement immutability? Not immutablitity, but use proxy for reactivity [02:49:11.0665] I have skimmed through the source, looks like they never revoke them. [02:49:19.0362] interesting, i see [02:49:32.0124] ```js const disposable = new DisposableStack() function createProxy() { const {proxy, revoke} = Proxy.revocable(...) disposable.defer(revoke) return proxy // at this point, revoke only hold by the DisposableStack. // engine can GC `revoke` and knowing the Proxy is being revoked after the DisposableStack disposes } ``` [02:51:35.0477] so do we have a non-theoretical go-to use case of revocation then? [02:52:19.0075] rkirsling: So far not, because at least Vue, does not seem to use _revocation_ [02:52:28.0029] Jack Works: according to the definition of liveness, clearing out the proxy target is not necessary, as the target's identity would be unobservable to the program through the proxy already [02:52:30.0266] > <@jackworks:matrix.org> ```js > const disposable = new DisposableStack() > function createProxy() { > const {proxy, revoke} = Proxy.revocable(...) > disposable.defer(revoke) > return proxy > // at this point, revoke only hold by the DisposableStack. > // engine can GC `revoke` and knowing the Proxy is being revoked after the DisposableStack disposes > } > ``` I mean, something like `new Proxy(target, handlers, { revocationStack: disposables })` might be feasible, but prefer this approach in general [02:53:51.0065] > <@mhofman:matrix.org> Jack Works: according to the definition of liveness, clearing out the proxy target is not necessary, as the target's identity would be unobservable to the program through the proxy already the [[ProxyHandler]] might get access to [[ProxyTarget]] when the internal slot is called right [02:54:01.0183] > <@rkirsling:matrix.org> so do we have a non-theoretical go-to use case of revocation then? membranes are not theoretical [02:55:06.0039] > <@jackworks:matrix.org> the [[ProxyHandler]] might get access to [[ProxyTarget]] when the internal slot is called right but if the proxy is revoked, the proxy handler would never be invoked with the target anymore [02:57:17.0053] An editor note might suffice to make it clear that once revoked, the proxy target's identity will no longer be observable by the program through the handler / proxy [02:57:47.0031] Thanks bterlson!! 👏 [02:59:01.0405] We will resume at 13:00 (62mins time) [02:59:14.0084] I _assume_ that SalesForce' whole [LightningLocker](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.security_locker_service) stuff being based on Proxies, they are also using _revocation_ [02:59:35.0045] if anyone here is on the typescript team and needs something to do over lunch I have a small QoL PR https://github.com/microsoft/TypeScript/pull/51457 [03:00:00.0400] I'm realising now that I have a LOT of tests to write for iterator helpers in test262... [03:00:20.0527] btw, link to the thread asking for a TG3 chair that I mentioned: https://github.com/tc39/Reflector/issues/450 [03:00:25.0309] yeah set methods will be fun too [03:01:17.0848] 🙏 hopefully a helpful community member will just do it before I get around to it [03:08:25.0177] i'm going to sleep because i'm sick and exhausted, but for Resource Management, i still think https://github.com/tc39/proposal-explicit-resource-management/issues/102 is not resolved, and i think any stage 3 for it needs to be conditional on coming to consensus on that. i hope my absence won't mean my concerns are overlooked (assuming that it otherwise would get stage 3, ofc) [03:09:53.0565] ljharb: is there a summary of your concern? [03:10:03.0048] or can ron characterize it accurately? [03:13:32.0072] IIRC, it's in regards to argument ordering of the `adopt` method, and thus it's existence entirely [03:15:30.0291] I can add a note to the slides to call this out [03:17:08.0812] great, thanks [03:21:55.0824] I missed a key point in my presentation: when we revoke a proxy, the _underlying slots_ are nullified, but the _proxy itself_ remains part of the object graph it lives in. You can't do anything with the proxy, but it's still there because (probably) something else refers to it. The revocation of an object graph, and of proxies pointing to it, means the original objects in that object graph can be gc'd. The proxies themselves are still reachable [03:57:06.0476] ljharb: Rob Palmer per the proposals index, I have transferred my GitHub repo to tc39-transfer. [04:03:58.0263] Markm is still having trouble joining [04:04:22.0247] tell him to try again [04:04:32.0084] I think I have admin rights to let people in now [04:04:50.0392] He did [04:05:37.0584] Thanks! [04:06:29.0937] It wasn't me - I suspect Yulia has done it [04:06:36.0391] nope -- it was ujjwal [04:06:38.0627] i don't have rights either [04:06:58.0799] from the shadows [04:07:43.0455] Permissions fixed [04:08:09.0364] > <@alex.vincent:matrix.org> I missed a key point in my presentation: when we revoke a proxy, the _underlying slots_ are nullified, but the _proxy itself_ remains part of the object graph it lives in. You can't do anything with the proxy, but it's still there because (probably) something else refers to it. The revocation of an object graph, and of proxies pointing to it, means the original objects in that object graph can be gc'd. The proxies themselves are still reachable As I mentioned above, explicitly emptying the slots is not actually necessary to make the targets eligible for collection once the proxy is revoked [04:21:01.0203] dumb question about this -- this would still be usable with streams in spite of the lack of async or no? [04:22:17.0858] yulia: basically no [04:22:41.0832] if the dispose method is async you need the async syntax, which is not in this version of the proposal [04:22:45.0381] what are current use cases that would be directly impacted here? [04:22:48.0529] and stream.cancel is async [04:24:00.0300] https://github.com/tc39/proposal-explicit-resource-management/#relation-to-dom-apis has a list [04:24:00.0584] I'm aware of the the atomic locking/unlocking behavior, but i think this is also in support of the shared structs proposal? [04:24:12.0172] about half of which is sync stuff [04:24:20.0786] ah great thanks [04:24:39.0770] also I really like https://github.com/whatwg/html/issues/8557#issuecomment-1331448189 [04:25:17.0286] thanks, that is useful [04:32:14.0218] > <@bakkot:matrix.org> also I really like https://github.com/whatwg/html/issues/8557#issuecomment-1331448189 adding in do-expressions to this would be nice, for ensuring the cancelation happens asap. ``` const pages = do { using controller = new AbortController.AutoAbort(); await Promise.all(urls.map(url => fetch(url, { signal: controller.signal })); }; ... // more logic and awaits follow ``` [04:32:42.0508] Ashley Claymore: that doesn't actually end up making any difference [04:33:00.0508] ah yes, Im thinking of Promise.race [04:33:06.0860] where it would [04:33:50.0120] ah, yeah [04:47:53.0075] Rob Palmer: is VERY quiet [04:57:43.0461] I should say also that I do not at all agree with markm's opinion that `{ using async x = y }` causing an `await` at block exit is a problem [04:57:51.0662] I would much prefer to just use that syntax [04:59:11.0246] it's not like it's super nested - the `using` statement is necessarily at the same level as the block where the `await` will happen; even if it's in the middle of the block, being at the top level of the block means it is inherently not that obscure [04:59:26.0952] * it's not like it's super nested - the `using` statement is necessarily at the same level as the block where the `await` will happen; even if it's in the middle of the block, being at the top level means it is inherently not that obscure [04:59:33.0250] * it's not like it's super nested - the `using` statement is necessarily at the same level as the block where the `await` will happen; even if it's in the middle of the block, being at the top level of the block means it is inherently not that obscure [05:00:59.0654] I tend to agree. Maybe I can help convince markm. [05:02:37.0598] +1 the partial class initialization thing is a pretty good motivation imo [05:08:48.0742] bakkot: in that short example maybe, but 1) currently all interleaving points within an async function are marked with `await`, and 2) with a block populated with other statements, the `using async` would not be as visible [05:10:32.0712] I really don't think it would be hard to see the `using async`, and I consider it to be a sufficient marker of the interleaving point [05:10:48.0757] we have found nested async interleaving to be a footgun today, and we're considering any further unclear interleaving as very undesirable [05:11:19.0241] I consider making `using async` harder to use very undesirable [05:11:24.0405] `async` is not a marker of interleaving. `await` is [05:11:26.0429] wait i don't understand why "async" isn't a clear indication of an interleaving point? [05:12:11.0739] well it's a marker that the thing contains interleaving points? [05:12:13.0406] async is a declaration that the thing has asynchronous behavior (returns a promise) [05:12:20.0639] * async is a declaration that the thing has asynchronous behavior (returns a promise) [05:12:22.0764] yes, you're declaring a thing that can contain interleaving points [05:12:40.0411] Congratulations rbuckton on Stage 3! I'm looking forward to _using_ the proposal in the wild 😀 [05:12:45.0956] no you're declaring it returns a promise [05:12:51.0540] await is the interleaving [05:13:02.0750] rbuckton: I would still appreciate seeing what your PluginHost example would look like if DisposableStack was not available. [05:13:25.0496] trying to join the call! can someone let me in? [05:13:48.0428] Mathieu Hofman: I just don't think the "it has to be exactly the `await` keyword, and not the `async` keyword" is a rule which is important [05:13:54.0553] who is that rule for? [05:14:31.0202] for people reading / auditing a piece of code [05:14:36.0739] right but like [05:14:41.0411] why is `async` not sufficient for those people [05:14:50.0465] more specifically why is `using async` insufficient [05:14:52.0452] because that's what async means today? [05:14:55.0156] treat it as a lexeme [05:14:59.0467] people are smart and adaptable [05:15:00.0637] > <@shuyuguo:matrix.org> wait i don't understand why "async" isn't a clear indication of an interleaving point? It's something erights wants to make very clear in the language, that `await` and `yield` are the *only* way to indicate interleaving points. `async` does not, since you must `await` the result of the async function to observe the result. [05:15:11.0694] yeah, what shu said [05:15:17.0703] right and I'm disagreeing with erights [05:15:30.0429] > <@shuyuguo:matrix.org> people are smart and adaptable you put more trust in people than I do [05:15:30.0872] I think `using async` being a new way to indicate an interleaving point is not a problem [05:15:31.0781] it's fine [05:15:35.0229] like this strikes me as "we must ship my mental model or we ship nothing", which doesn't seem great? [05:15:49.0244] * I think `using async` being a new way to indicate an interleaving point is not a problem [05:15:53.0279] Rob Palmer: can you help? trying to join the call. [05:16:10.0214] I don't see why we're so willing to throw away the consistency of the language we have today [05:16:30.0383] thanks [05:16:31.0511] done [05:16:42.0198] because it's not an important consistency, especially when weighed against the cost of only being able to put `using async` inside `if` statements [05:16:49.0999] the second thing is much, much more important [05:17:03.0090] > <@bakkot:matrix.org> right and I'm disagreeing with erights The relevant discussion can be found in https://github.com/tc39/proposal-explicit-resource-management/issues/101. This argument has been explicitly called out here: - https://github.com/tc39/proposal-explicit-resource-management/issues/101#issuecomment-1320609180 - https://github.com/tc39/proposal-explicit-resource-management/issues/101#issuecomment-1320789790 [05:17:31.0440] in general there are many surface consistency things we ought to trade off against other things [05:17:40.0251] argument order and coercion order and so forth is often another one [05:17:41.0571] we consider `await` in conditional statements to be a footgun, so that doesn't have much weight in our opinion [05:17:46.0608] well [05:17:50.0007] that's... [05:17:52.0389] uh. [05:17:57.0529] not an opinion I agree with. [05:18:23.0398] well my response to that is, sorry you don't get to ship an implicit linter in the language [05:18:24.0833] you are welcome to not put `await` in conditional statements but I really do not approve of you designing the language to stop me from doing it [05:18:28.0982] yes [05:18:45.0196] I don't understand that position either. `await` in a conditional doesn't "release zalgo", so such a constraint seems confusing. [05:18:46.0406] right, I think we fundamentally disagree with the ability to reason about interleaving points. I don't see why an explicit block marker is so onerous [05:19:43.0652] > <@rbuckton:matrix.org> I don't understand that position either. `await` in a conditional doesn't "release zalgo", so such a constraint seems confusing. it actually does because of the current unsafety of `PromiseResolve` [05:19:46.0006] > <@shuyuguo:matrix.org> well my response to that is, sorry you don't get to ship an implicit linter in the language `??` would like to disagree with you. I was not in favor of requiring parens to disambiguate with `||` and `&&` given clear precedence rules, but here we are. [05:20:38.0911] No, "releasing zalgo" is writing an API such that _consumers_ cannot safely reason over whether that API will execute synchronously or asynchronously. [05:21:39.0099] If the function is async, it will always _complete_ in a later turn, but will start executing in _this turn_. [05:21:43.0213] Mathieu Hofman I think that most people have not found `await` in conditional statements to actually be a problem. so I think if you are reasoning about your code in a way where it is a problem, the appropriate way for you to enforce that is by writing your own linting rules, which forbid _both_ `await` in conditional statements _and_ `using async` in blocks, and let the rest of us have both of those things [05:23:06.0589] anyway, I think we deviated a little, and the conditional thing is not that relevant to this discussion. `async using` is a declaration that the value has an async behavior. It does nothing to indicate that there will later, at the end of the current scope, be an interleaving point, which are today all marked with an explicit `await` keyword [05:23:25.0060] it's a new piece of syntax, and you have to learn what it means [05:23:30.0619] > <@bakkot:matrix.org> Mathieu Hofman I think that most people have not found `await` in conditional statements to actually be a problem. so I think if you are reasoning about your code in a way where it is a problem, the appropriate way for you to enforce that is by writing your own linting rules, which forbid _both_ `await` in conditional statements _and_ `using async` in blocks, and let the rest of us have both of those things we have [05:23:30.0913] I really don't think it's that hard to learn [05:24:25.0964] Mathieu Hofman: I accept that you have, but stand by my statement, and my position that the appropriate resolution for you is to have lint rules which enforce the stronger invariants that you apparently need; most people, in my experience, do not need them, so we should not bake them into the language. [05:26:09.0818] again I'm simply asking for the language to stay consistent with itself and mark interleaving points at the location of interleaving [05:26:31.0837] and I'm saying I don't think that consistency is worth the cost of making `using async` harder to use. [05:26:48.0008] given that it is, in my opinion, really not that hard to learn what `using async` does. [05:27:09.0967] what's insane to me is that adding a `async using` statement to a plain block introduces an interleaving point at a distance [05:27:15.0059] > <@bakkot:matrix.org> Mathieu Hofman: I accept that you have, but stand by my statement, and my position that the appropriate resolution for you is to have lint rules which enforce the stronger invariants that you apparently need; most people, in my experience, do not need them, so we should not bake them into the language. My first response years ago to erights 's concern about explicit `await` was to use linter to require something like `// await using` at the end of a block, but that didn't seem to be sufficient at the time. [05:27:26.0872] it's a refactor hazard [05:27:45.0067] it does not seem like any more of a refactoring hazard than the sync call to `Symbol.dispose()` is [05:27:50.0872] or at least not very much more [05:28:05.0707] let's take a step back, what is the thing that you are doing that makes you care so much about interleave points? [05:29:36.0431] being able to reason when your synchronous execution has been interrupted, and thus your state may have gotten mutated. [05:30:00.0618] why is that not a concern with sync dispose [05:30:38.0275] Any time you run code you run the risk of side effects. `+value` can have side effects. An implicit `await` would have side effects, but that's far easier to run into than a side-effecty `+value` or the like. If you're writing code where an implicit async interleaving point could be a problem (i.e., potential state mutations in closed-over code), you have the same problem with any `await` and would likely need to use some type of async coordination primitive to synchronize access. [05:31:09.0243] like i agree this is a problem, which is why i added "can call user code", but that's not specific to async [05:31:21.0681] One point in the favor of the explicit marker is that its harder to recognize you might need to coordinate access if the interleave point is implicit. [05:31:25.0238] because you control the resource and what it does. You do not control what other promise jobs may have done if suspended [05:31:44.0818] "you control the resource" is in practice, not true [05:32:43.0280] some thought for the current problem: [05:32:48.0401] I attempted to make the point that a declaration like `async using x = ...` would be sufficient to indicate such coordination might be necessary. [05:32:59.0254] ```worker.addModule(module {}, import.meta.url)``` [05:33:28.0866] if your model allows you to trust all the objects you invoke synchronously, you don't have to worry about synchronous re-entrancy [05:34:10.0236] * if your model allows you to trust all the objects you invoke synchronously, you don't have to worry about synchronous re-entrancy [05:35:07.0320] I think the subset of programs in which people are sufficiently careful that they are able to make and maintain an accurate model which allows them to trust all the objects they invoke synchronously, but where the code is being read so quickly that `using async` does not suffice as a marker of an interleaving point, is quite small [05:35:25.0660] i daresay that subset of programs is exceedingly small [05:36:15.0844] Mathieu Hofman: ISTM your position is predicated on a very opinionated environment that does not generalize, and thus should not be baked into the language [05:40:48.0551] Could someone let me back into the meeting? (My internet cut out, so I got dropped.) [05:41:54.0574] thx [05:45:13.0835] a heads up: we are overrunning in this session. therefore it is possible the meeting may end 15 minutes late in order to give shadow realms the full 60mins [05:46:51.0624] > <@rbuckton:matrix.org> `??` would like to disagree with you. I was not in favor of requiring parens to disambiguate with `||` and `&&` given clear precedence rules, but here we are. (btw ~3 years in since stage 4 and it turns out nobody cares) [05:47:34.0035] hm that's a good data point [05:48:06.0102] I get frustrated by it every now and then. [05:48:18.0809] So the number is non-zero, even if close to zero. [05:51:39.0137] Let it be known that Ron runs into it :D [05:55:49.0091] did iterator helpers get stage 3? [05:56:35.0080] snek: yes it did! [05:56:43.0336] https://twitter.com/robpalmer2/status/1598248214750191616?s=20&t=6pFlDUfZtj5hXI1s5l9ohw [05:56:49.0948] hypeeee [05:56:52.0580] snek: https://mastodon.social/@robpalmer/109437830520093382 [05:57:25.0159] ooo now which one do I click on [06:00:48.0772] Justin Ridgewell: I want to push back on the statement that module expressions are not useful without module declarations. I would argue that module expressions have very strong motivation, separate from motivation for module declaration. [06:01:26.0213] Motivations aside, there is so much shared between them its difficult to talk about them independently. [06:01:46.0050] > <@rbuckton:matrix.org> Motivations aside, there is so much shared between them its difficult to talk about them independently. I think it is reasonable to talk about expressions without declarations, but not the other way around. [06:01:48.0250] the fact that the expressions cannot be used to import from, is sufficient for me to keep them independently. [06:02:19.0278] > <@lucacasonato:matrix.org> I think it is reasonable to talk about expressions without declarations, but not the other way around. Reason being that there is a future where expressions get shipped, but not module declarations. [06:02:28.0855] One can exist and be useful without the other [06:04:10.0196] Are nested modules scoped to within the parent module? [06:04:16.0707] > <@rpamely:matrix.org> Are nested modules scoped to within the parent module? Yes [06:04:32.0964] Unless you explicitly export them [06:05:06.0806] the real reason those proposals are separated IMO because they're initially separated presented to the committee (and back to days they're stage 0, they don't looks similar too much) [06:05:43.0255] Are modules independently cachable in browsers? If Site A serves lodash, can Site B use that? [06:06:00.0373] > <@rpamely:matrix.org> Are modules independently cachable in browsers? If Site A serves lodash, can Site B use that? nowadays I believe answer is not [06:06:05.0038] Ie: ```ts module foo { module bar {} export module baz {} } import bar; // Linking error import baz; // Linking error import { baz } from foo; import baz; // Success! ``` [06:06:18.0934] I've heard browsers are shipping cross-site cache isolation to prevent tracking [06:06:22.0928] * Ie: ```ts module foo { module bar {} export module baz {} } import bar; // Linking error import baz; // Linking error import { baz } from foo; import baz; // Success! ``` [06:07:00.0031] besides, practically speaking, due to the widespread use of bundlers it's not very likely [06:07:22.0785] everyone bundles their own React 🤔 [06:07:54.0715] > <@usharma:igalia.com> besides, practically speaking, due to the widespread use of bundlers it's not very likely Right, but with module declarations you can now cache module based on some other identifier than URL [06:07:59.0949] so bundled modules could be cached [06:08:08.0838] security concerns aside [06:09:48.0797] > <@rpamely:matrix.org> so bundled modules could be cached I strongly against to serialize a ModuleSource and store it on the disk for security reasons. 🆗 post message to Worker ⛔ store it in indexedDB and read it in the future [06:10:31.0326] if we allowed over the wire or on disk serialization of module expressions we'd need a formal binary AST [06:11:03.0748] > <@rbuckton:matrix.org> if we allowed over the wire or on disk serialization of module expressions we'd need a formal binary AST indexedDB does not show developers how data is stored so that's pure implementation detail. [06:11:30.0571] Why is it any different than caching the react source from a CDN, if you cache the react source from a bundled site? [06:11:52.0388] (I'm thinking out loud here) [06:13:16.0226] I’m reasonably certain that module declarations would break new Module((module {}).source), which is key to user composition of module graphs. [06:13:23.0756] > <@rpamely:matrix.org> Right, but with module declarations you can now cache module based on some other identifier than URL yes! I wonder if this has interesting implications for, say, ad-blocking. [06:14:19.0908] > <@rpamely:matrix.org> Why is it any different than caching the react source from a CDN, if you cache the react source from a bundled site? there is no longer caching from CDNs https://github.com/whatwg/fetch/issues/904 [06:14:24.0051] I'm not huge on needing a separate namespace per module (or bit per value) to track which exports are *module* exports that can be imported from vs. not https://gbracha.blogspot.com/2014/09/a-domain-of-shadows.html [06:16:03.0438] * I'm not huge on needing a separate namespace per module (or bit per value) to track which exports are _module_ exports that can be imported from vs. not This blog post comes to mind https://gbracha.blogspot.com/2014/09/a-domain-of-shadows.html [06:16:29.0215] Module declarations/expressions won’t compose because a module graph that consists of declarations coming from different module files cannot be consolidated into a single module handler, for the purposes of resolving stringly named module specifiers on their various referrers. [06:16:42.0241] Curious about static importability and portability of `import module foo from ...` and `module bar { import foo; }`. Is that valid? [06:17:20.0705] If I'm reading this correctly, the concern was that CDN sources were manipulated to add tracking code in them that got loaded into every site that uses them? [06:17:35.0445] > <@bakkot:matrix.org> there is no longer caching from CDNs https://github.com/whatwg/fetch/issues/904 * If I'm reading this correctly, the concern was that CDN sources were manipulated to add tracking code in them that got loaded into every site that uses them? [06:17:49.0435] aren't module declarations top level only? [06:17:59.0009] Slides: https://docs.google.com/presentation/d/183cYPeUjhzVRuZ0O-gwXqp6KtFYxxhgYodbr7XNo_9o/edit [06:18:15.0909] And what about: ```js const x = module { } const y = module { import x; } ``` There is so much inconsistency with the "two worlds" approach that breaks the decl/expr equivalence of function and class [06:18:48.0941] > <@leobalter:matrix.org> aren't module declarations top level only? The proposal as it is now allows you to do something like this: ```js { module x {} await import(module { import x }); } ``` [06:19:14.0758] thanks for the info [06:19:19.0765] That's specific to just that case, not a general solution. [06:19:22.0759] My other concern is that differentiating lexical and stringy module specifiers will be used to distinguish which modules should be serialized from which should be resolved from a remote worker’s import hooks, and will have ramifications on whether instances are shared. That’ll herd the module ecosystem in a weird direction, where portable libraries will generally have to export a module expression/declaration in order to give the user the choice of whether to link lazily or eagerly. [06:19:28.0040] > <@rbuckton:matrix.org> And what about: > ```js > const x = module { } > const y = module { import x; } > ``` > There is so much inconsistency with the "two worlds" approach that breaks the decl/expr equivalence of function and class What would you expect to happen here if instead of `const x` you had a `let x`, that is later reassigned? [06:19:50.0121] You could be importing a `module` dynamically from another module that exports it, but can't reference it in another `module` expression. [06:19:52.0408] Robert Pamely: no, the concern is, my site can tell you've visited another site by timing whether loading a resource which is also loaded by that site is cache-hit or not [06:19:57.0843] https://github.com/shivanigithub/http-cache-partitioning has a better explainer I think [06:19:58.0855] I think it may be a reasonable to investigate using a seperate identifier namespace to clarify the difference between "regular values" and "module values". For example `module $foo {}; import $foo;` [06:20:38.0013] > <@lucacasonato:matrix.org> I think it may be a reasonable to investigate using a seperate identifier namespace to clarify the difference between "regular values" and "module values". For example `module $foo {}; import $foo;` but module declarations should be able to `import($foo)` [06:20:48.0376] so there is no need to separate a new namespace [06:20:59.0628] > <@rbuckton:matrix.org> Curious about static importability and portability of `import module foo from ...` and `module bar { import foo; }`. Is that valid? There is no spec text for this yet, but if import reflection reifies to a `Module` object it can be easily made to work. [06:21:15.0791] we can think of, some values are just available eariler, before the execute [06:21:26.0809] ```js // foo.js export module A { } // bar.js export function maybeDoAThing() { const A = await import("./foo.js").A; const B = module { import A }; new Worker(B).postMessage(...); } ``` [06:21:36.0062] > <@jackworks:matrix.org> we can think of, some values are just available eariler, before the execute this is difficult to explain to users though. using a separate namespace would make this clear [06:22:05.0043] > <@rbuckton:matrix.org> ```js > // foo.js > export module A { } > > // bar.js > export function maybeDoAThing() { > const A = await import("./foo.js").A; > const B = module { import A }; > new Worker(B).postMessage(...); > } > ``` I believe this is a link error. [06:22:29.0024] it's difficult to explain why this is the case: ```js const a = module {}; module b {}; import a; // link error import b; // fine ``` [06:22:31.0511] My point is that the "2nd world" approach makes module decls and module expressions inconsistent. [06:22:34.0810] * it's difficult to explain why this is the case: ```js const a = module {}; module b {}; import a; // link error import b; // fine ``` [06:22:41.0448] That's a refactoring hazard. [06:23:04.0168] Module expressions and declarations are among the most important changes coming to ECMAScript that positively affects web developers and code bundling in order to encourage a better usage of ES modules. I hope TC39 can dedicate more time for those topics in order to advance them without taking years. [06:23:15.0403] > <@lucacasonato:matrix.org> it's difficult to explain why this is the case: > > ```js > const a = module {}; > module b {}; > import a; // link error > import b; // fine > ``` so maybe we can also accept `const ident = module {}`. no more complex [06:23:22.0208] * Module expressions and declarations are among the most important changes coming to ECMAScript that positively affects web developers and code bundling in order to encourage a better usage of ES modules. I hope TC39 can dedicate more time for those topics in order to advance them without taking years. [06:23:40.0880] Thanks for the example, the only way it could work with this proposal is something like this (!!! semantics are different, but the intention is the same): ```js // foo.js export module A { } // bar.js export function maybeDoAThing() { const B = module { import { A } from "./foo.js"; import A }; new Worker(B).postMessage(...); } ``` It's worth investigating when modules should "capture" available modules from the other scope, but it would still only allow capturing other modules and not generic values. [06:23:58.0833] > <@jackworks:matrix.org> so maybe we can also accept `const ident = module {}`. no more complex we can't hoist const bindings though. module declarations only work statically, because they are hoisted [06:24:07.0123] * Thanks for the example, the only way it could work with this proposal is something like this: ```js // foo.js export module A { } // bar.js export function maybeDoAThing() { const B = module { import { A } from "./foo.js"; import A }; new Worker(B).postMessage(...); } ``` It's worth investigating when modules should "capture" available modules from the other scope, but it would still only allow capturing other modules and not generic values. [06:24:31.0166] * Thanks for the example, the only way it could work with this proposal is something like this (!!! semantics are different, but the intention is the same): ```js // foo.js export module A { } // bar.js export function maybeDoAThing() { const B = module { import { A } from "./foo.js"; import A }; new Worker(B).postMessage(...); } ``` It's worth investigating when modules should "capture" available modules from the other scope, but it would still only allow capturing other modules and not generic values. [06:24:31.0873] I think if we have two things called `module` where there is a declaration and an expression version, then what they contain and how they handle scoping should be consistent. [06:25:20.0740] shu: currently the message and name access is specced to be non-observable, which mimics FF, but if there's an argument for otherwise, we could spec to always perform a Get [06:25:23.0354] > <@rbuckton:matrix.org> I think if we have two things called `module` where there is a declaration and an expression version, then what they contain and how they handle scoping should be consistent. Well, your examples are about scoping of module declarations and const declarations [06:25:51.0151] > <@rbuckton:matrix.org> ```js > // foo.js > export module A { } > > // bar.js > export function maybeDoAThing() { > const A = await import("./foo.js").A; > const B = module { import A }; > new Worker(B).postMessage(...); > } > ``` Some thought: Link of module B in your example can be deferred to when `maybeDoAThing` is called. When the expression is evaluated, it checks if `A` is a Module and if it is a constant binding, if not, it failed to evaluate. [06:25:55.0654] * shu: currently the message and name access is specced to be non-observable, which mimics FF, but if there's an argument for otherwise, we could spec to always perform a Get [06:27:19.0316] > <@nicolo-ribaudo:matrix.org> Well, your examples are about scoping of module declarations and const declarations Yes, because that could be a real use case. If you can dynamically import a module expression, then you can do so conditionally. [06:27:26.0407] uhhh censoring error stacks is already a proposal [06:30:18.0795] bakkot: `user land -> userland` [06:31:10.0977] > <@jackworks:matrix.org> Some thought: > > Link of module B in your example can be deferred to when `maybeDoAThing` is called. When the expression is evaluated, it checks if `A` is a Module and if it is a constant binding, if not, it failed to evaluate. Perhaps? This goes to my point about closures. You might, for example need to indicate if a given binding/value is either transparently serializable, replaceable (such as globals), or banned (i.e., non-serializable, non-global). [06:34:28.0499] Accessing a serializable or replaceable binding in a module would be fine, but other bindings would not. There's a circularity issue there though: ```js const a = module { export function f() { return import(b); } }; const b = module { export function g() { return import(a); } }; ``` This seems like a perfectly reasonable case, but you can't eagerly serialize `b` because it's not defined. You'd have to defer such serialization until it is needed, so you would again have to capture all bindings. [06:36:44.0693] `a` and `b` aren't capturable in that example though, right? [06:37:07.0672] > <@danielrosenwasser:matrix.org> `a` and `b` aren't capturable in that example though, right? My contention is that they should be. [06:38:08.0176] > <@rbuckton:matrix.org> Accessing a serializable or replaceable binding in a module would be fine, but other bindings would not. There's a circularity issue there though: > ```js > const a = module { export function f() { return import(b); } }; > const b = module { export function g() { return import(a); } }; > ``` > This seems like a perfectly reasonable case, but you can't eagerly serialize `b` because it's not defined. You'd have to defer such serialization until it is needed, so you would again have to capture all bindings. I agree this becomes messy... `b` and `a` will be looked up in the global object of it's evaluator instead of links to the module [06:38:18.0983] It would be extremely inconsistent if you cannot model with module expressions the same thing you can model with module declarations. This seems like a major stumbling block for adopters. [06:38:26.0600] oh, because they *would* be valid if it was written as ``` module a { export function f() { return import(b); } }; module b { export function g() { return import(a); } }; ``` [06:38:27.0495] Justin Ridgewell: they don't want `if (runningInShadowRealm)` [06:38:45.0833] wait...is **_that_** true though? [06:38:53.0904] * ...is **_that_** true though? [06:39:09.0212] If it isn't then we don't have the same kind of decl/expr parity that function and class have. [06:39:09.0787] * wait...is **_that_** true though? [06:39:13.0541] > <@danielrosenwasser:matrix.org> oh, because they *would* be valid if it was written as > > ``` > module a { export function f() { return import(b); } }; > module b { export function g() { return import(a); } }; > ``` Yes, this would be valid [06:39:50.0472] I'd almost prefer just having module declarations over having both, if only to prevent potential confusion by adopters due to the inconsistency. [06:39:55.0603] > <@rbuckton:matrix.org> If it isn't then we don't have the same kind of decl/expr parity that function and class have. Functions already don't have parity - they have different scoping rules. Only classes can be transparently replaced with `let className = class { ...` [06:40:17.0474] So if you're running `a` in a context where `globalThis.b` exists, then a captured `b` takes precedence? [06:42:45.0237] > <@nicolo-ribaudo:matrix.org> Functions already don't have parity - they have different scoping rules. Only classes can be transparently replaced with `let className = class { ...` The difference in outer scoping is that a function declaration is hoisted, while a class has TDZ. [06:42:53.0674] we could just advance the error stacks proposal... [06:43:42.0440] this would be a very small change on top of that [06:49:50.0676] > <@danielrosenwasser:matrix.org> So if you're running `a` in a context where `globalThis.b` exists, then a captured `b` takes precedence? If you have this code: ```js module a { let b = getAModule(); await import(b); }; module b { await import(a); }; ``` the dynamic `import(b)` imports the result of `getAModule` and not the outer declaration. That looks expected. In this case: ```js module a { globalThis.b = getAModule(); await import(b); }; module b { await import(a); }; ``` I see that it would be confusing, since `globalThis.b =` might be lexically somewhere else and not analyzable. How would you feel if bindings of module declarations where not captured, but only statically importable? ```js module a {} module b { import a; } // ok ``` ```js module a {} module b { console.log(a); } // ReferenceError: as every other type of bindings, you cannot capture bindings from the outer scope. You can only use them in import specifiers, which is not a generic "binding usage" position ``` Note: the current spec text throws the RandeError in this case, but just because it was easier to specify. We didn't have a reason to do one thing or the other, but this possible confusion seems to indicate what we should do. [06:50:20.0412] > <@danielrosenwasser:matrix.org> So if you're running `a` in a context where `globalThis.b` exists, then a captured `b` takes precedence? * If you have this code: ```js module a { let b = getAModule(); await import(b); }; module b { await import(a); }; ``` the dynamic `import(b)` imports the result of `getAModule` and not the outer declaration. That looks expected. In this case: ```js module a { globalThis.b = getAModule(); await import(b); }; module b { await import(a); }; ``` I see that it would be confusing, since `globalThis.b =` might be lexically somewhere else and not discoverable. How would you feel if bindings of module declarations where not captured, but only statically importable? ```js module a {} module b { import a; } // ok ``` ```js module a {} module b { console.log(a); } // ReferenceError: as every other type of bindings, you cannot capture bindings from the outer scope. You can only use them in import specifiers, which is not a generic "binding usage" position ``` Note: the current spec text throws the RandeError in this case, but just because it was easier to specify. We didn't have a reason to do one thing or the other, but this possible confusion seems to indicate what we should do. [06:50:41.0107] * If you have this code: ```js module a { let b = getAModule(); await import(b); }; module b { await import(a); }; ``` the dynamic `import(b)` imports the result of `getAModule` and not the outer declaration. That looks expected. In this case: ```js module a { globalThis.b = getAModule(); await import(b); }; module b { await import(a); }; ``` I see that it would be confusing, since `globalThis.b =` might be lexically somewhere else and not discoverable. How would you feel if bindings of module declarations where not captured, but only statically importable? ```js module a {} module b { import a; } // ok ``` ```js module a {} module b { console.log(a); } // ReferenceError: as every other type of bindings, you cannot capture bindings from the outer scope. You can only use them in import specifiers, which is not a generic "binding usage" position ``` Note: the current spec text throws the RandeError in this case, but just because it was easier to specify. We didn't have a reason to do one thing or the other, but this possible confusion seems to indicate what we should keep the error. [06:51:17.0339] * If you have this code: ```js module a { let b = getAModule(); await import(b); }; module b { await import(a); }; ``` the dynamic `import(b)` imports the result of `getAModule` and not the outer declaration. That looks expected. In this case: ```js module a { globalThis.b = getAModule(); await import(b); }; module b { await import(a); }; ``` I see that it would be confusing, since `globalThis.b =` might be lexically somewhere else and not discoverable. How would you feel if bindings of module declarations where not captured, but only statically importable? ```js module a {} module b { import a; } // ok ``` ```js module a {} module b { console.log(a); } // ReferenceError: as every other type of bindings, you cannot capture bindings from the outer scope. You can only use them in import specifiers, which is not a generic "binding usage" position ``` Note: the current spec text throws the ReferenceError in this case, but just because it was easier to specify. We didn't have a reason to do one thing or the other, but this possible confusion seems to indicate what we should keep the error. [06:51:58.0042] this sounds like a topic for TG3... [06:52:44.0421] I'm not convinced by the hopelessness being expressed [06:53:13.0917] Is this the only way that script can determine it's running in a shadow realm? [06:53:25.0723] (currently known) [06:53:49.0241] I mean it's non-standard, there could be non-standard "amIRunningInAShadowReam" function [06:57:06.0525] (thanks btw danielrosenwasser and rbuckton for explaining your concerns with clear code examples!) [06:59:29.0141] > <@nicolo-ribaudo:matrix.org> If you have this code: > > ```js > module a { let b = getAModule(); await import(b); }; > module b { await import(a); }; > ``` > > the dynamic `import(b)` imports the result of `getAModule` and not the outer declaration. That looks expected. > > In this case: > > ```js > module a { globalThis.b = getAModule(); await import(b); }; > module b { await import(a); }; > ``` > > I see that it would be confusing, since `globalThis.b =` might be lexically somewhere else and not discoverable. > > How would you feel if bindings of module declarations where not captured, but only statically importable? > > ```js > module a {} > module b { import a; } // ok > ``` > > ```js > module a {} > module b { console.log(a); } // ReferenceError: as every other type of bindings, you cannot capture bindings from the outer scope. You can only use them in import specifiers, which is not a generic "binding usage" position > ``` > > Note: the current spec text throws the ReferenceError in this case, but just because it was easier to specify. We didn't have a reason to do one thing or the other, but this possible confusion seems to indicate what we should keep the error. I think if we had module declarations, I would prefer the "do not implicitly capture" behavior, but it feels like that defeats a lot of what would make them "special". I'm not sure if that's the right answer, but I found the current behavior confusing because my mental model was "modules capture _nothing_" [06:59:49.0302] > <@michaelficarra:matrix.org> I mean it's non-standard, there could be non-standard "amIRunningInAShadowReam" function Shipping a standard "amIRunningInAShadowRealm" would be the most extreme form of telling people script can change its behavior based upon how it is run, as opposed to relying on them reading spec text, implementation recommendation, or whatever it is called. [07:02:19.0209] Here is the open issue about invariants: https://github.com/tc39/proposal-shadowrealm/issues/324 [07:04:13.0885] > <@michaelficarra:matrix.org> we could just advance the error stacks proposal... perfect, I will drop the ball into that bucket by opening an issue there :) [07:05:33.0232] i had enough in me to stay awake until 7, barely, but I am very close to falling asleep before we finish this item [07:06:24.0181] commiserations to everyone who ended up in the overflow [08:49:51.0483] Could a chair help me by finishing the transfer of https://github.com/tc39-transfer/proposal-async-explicit-resource-management to the tc39 org to help with the async resource management split? [11:42:05.0142] > <@rbuckton:matrix.org> Could a chair help me by finishing the transfer of https://github.com/tc39-transfer/proposal-async-explicit-resource-management to the tc39 org to help with the async resource management split? I still need owner access so that I can make changes to Settings and set up CI/publishing for the rendered spec. [12:00:06.0511] rbuckton: done [12:01:00.0862] * rbuckton: done [12:01:37.0268] Thanks! [13:59:06.0531] > <@danielrosenwasser:matrix.org> I think if we had module declarations, I would prefer the "do not implicitly capture" behavior, but it feels like that defeats a lot of what would make them "special". I'm not sure if that's the right answer, but I found the current behavior confusing because my mental model was "modules capture _nothing_" Honestly it sounds like we're starting to get at, module fragments should be in a separate namespace. Neither variables nor strings, but a secret third thing. As an obviously unusable strawman, let's say they begin with `##` :). So the example would be: ```js module ##a { let b = getAModule(); await import(b); /* obviously targets the lexically scoped variable */ } module ##b { import ##a; /* obviously targets the module declaration */ } ``` [13:59:40.0535] this separate namespace could actually be usable in expression context (to get the Module object), just not the opposite: it would be impossible to make a runtime variable with the same name [14:00:02.0215] so then we'd have a clear explanation: `module { }` closes over just the `##xyz` values, and nothing else [14:01:17.0415] remember that we previously rejected strings specifiers for a couple reasons: - We wanted to leave 100% of the string specifier space to hosts. For example, we definitely didn't want to steal the fragment space of URLs, which already has other semantics. - At the same time, we wanted module declarations to have well-defined, reliable, host-independent semantics [14:02:23.0372] * remember that we previously rejected strings specifiers for a couple reasons: - We wanted to leave 100% of the string specifier space to hosts. For example, we definitely didn't want to steal the fragment space of URLs, which already has other semantics. - At the same time, we wanted module declarations to have well-defined, reliable, host-independent semantics [14:03:35.0421] rbuckton: I do want to confer with you sometime in December (but not in the next few days) about the resource management, cancellation, and proxy revocation proposals. Do you attend SES's strategy sessions occasionally? [14:24:06.0695] oof - a parallel namespace gives me all sorts of bad feelings. the committee reacted pretty strongly against that with hax's extensions proposal too, iirc [14:25:12.0562] well, we made a separate namespace for private names :) Arguably the specifiers already *are* a separate namespace, as well. I think the viability depends a lot on if we can come up with an agreeable syntax. [14:26:04.0771] also it depends whether a proposal "pays for itself" (introducing a significant enough new capability) and meets developer expectations (For extensions, I think developers hope it will magically work with `x.y()`) [14:29:22.0550] i would think dev expectations of module fragments/blocks is that they either capture everything, like a function, or nothing at all (in which case the way to "pass in" something would be like in php's `using` declaration on closures) [14:32:45.0356] Yeah, another way around this is another top-level production/mimetype (like web bundles, but it has to be processable just by the JS engine). That introduces other issues, though. [14:33:44.0570] > <@ljharb:matrix.org> i would think dev expectations of module fragments/blocks is that they either capture everything, like a function, or nothing at all (in which case the way to "pass in" something would be like in php's `using` declaration on closures) Honestly I don't understand how you arrived at such a strong analysis of developer expectations so quickly. [14:34:03.0775] "would think" is particularly strong? [14:34:16.0217] never mind [14:35:13.0810] i would also think that any of us that have extensive experience interacting with devs in general would indeed have some sense, immediately, of what might be suitable, even tho more research is always helpful 2022-12-02 [16:23:45.0827] > <@alex.vincent:matrix.org> rbuckton: I do want to confer with you sometime in December (but not in the next few days) about the resource management, cancellation, and proxy revocation proposals. Do you attend SES's strategy sessions occasionally? I haven't so far, mostly due to time constraints. I can make some time though. [16:28:17.0138] My suggestion was to just close over regular bindings, and when serializing determine if the binding is: 1. rewritable (i.e., globals) 2. serializable (such as another `module`) 3. non-portable (anything else) and throw if there are any non-portable values. A side benefit would be that it would be an error to accidentally reference a variable that shadows a global. [16:28:40.0867] * My suggestion was to just close over regular bindings, and when serializing determine if the binding is: 1. rewritable (i.e., globals) 2. serializable (such as another `module`) 3. non-portable (anything else) and throw if there are any non-portable values. A side benefit would be that it would be an error to accidentally reference a variable that shadows a global. [16:29:38.0541] To further clarify, we'd determine if a _binding_ is rewritable or not rewritable, and if the binding is not rewritable, determine if its _value_ is portable (i.e., serializable) or non-portable. [16:30:06.0573] When blöcks was presented, there was a lot of skepticism about this particular aspect of the proposal. It is very deliberate that module blocks omits it. [16:31:19.0235] I honestly don’t understand how we could make this mechanism work well enough, given how dynamic JS is and how this committee tries to meet expectations of all the edge cases working well (eg with eval) [16:31:45.0546] I like that you have some details spelled out though [16:32:45.0145] IIRC, the blöcks proposal wanted you to substitute the bindings explicitly when you instantiated the block [16:32:57.0831] In particular I don’t know how “rewritable” would work. Module blocks is content with just reevaluating in the other realm. [16:33:29.0309] > <@littledan:matrix.org> In particular I don’t know how “rewritable” would work. Module blocks is content with just reevaluating in the other realm. That's exactly what I mean by rewritable: "these are the things that you just reevaluate in the other realm" [16:33:36.0922] Ah Ok [16:34:05.0021] The current proposal allows you to lexically reference another `module` and explicitly breaks that model, since you're not just looking up that name in another realm. [16:34:21.0843] * The current proposal allows you to lexically reference another `module` and explicitly breaks that model, since you're not just looking up that name in another realm. [16:34:38.0139] So the other thing is, given module blocks, you can define a system which serializes a list of things and then passes them as arguments to a default-exported function. I guess a bit more unergonomic due to the duplication though. [16:35:16.0766] > <@littledan:matrix.org> So the other thing is, given module blocks, you can define a system which serializes a list of things and then passes them as arguments to a default-exported function. I guess a bit more unergonomic due to the duplication though. I had thought of that approach when I discussed the proposal with my team on Monday. [16:35:30.0078] > <@rbuckton:matrix.org> The current proposal allows you to lexically reference another `module` and explicitly breaks that model, since you're not just looking up that name in another realm. Yeah, I can empathize with this; this is why I raised the separate namespace idea above [16:36:02.0512] Another approach would be to go the WASM route where you have to explicitly provide any external module references when instantiating the `Module`, which would mean no lexical scoping. [16:36:39.0013] That would still work with bundlers, though they'd need to emit a chunk of code at the bottom of the file that linked all of the declarations together. [16:36:53.0662] * Another approach would be to go the WASM route where you have to explicitly provide any external module references when instantiating the `Module`, which would mean no lexical scoping. [16:37:12.0734] Yeah, Nicolo has sketched out this chunk of code in a gist. It would work with module expressions + the importHook in the module constructor [16:37:41.0912] I got the impression that V8 was highly skeptical of this hook. Module declarations are more limited in expressiveness; more “static” [16:37:59.0823] Anyway there are probably bundler use cases that need the dynamic hook [16:38:49.0064] If there was no lexical closure for modules, you could still have `import {} from id` but you'd need to define those ids when instantiating the module, i.e. `await import(module { import foo; }, { bindings: { foo: module {} } })` [16:39:01.0529] > <@rbuckton:matrix.org> That would still work with bundlers, though they'd need to emit a chunk of code at the bottom of the file that linked all of the declarations together. This is consistent with what’s possible with just module expressions and the Module constructor. [16:39:22.0765] That would be roughly analogous to the blocks proposal where you'd need to supply values for bindings. [16:40:17.0439] * That would be roughly analogous to the blöcks proposal where you'd need to supply values for bindings. [16:41:05.0712] Concretely, this is Nicolò’s sketch of a module expression and Module constructor bundle generator https://gist.github.com/nicolo-ribaudo/81f18db096659ac8447ca94f50f2c37a [16:41:57.0487] You could even do that with module declarations and stitch them together using a `with` clause: ```js module foo { } module bar { import foo } // NOTE: not a closure, just an unsatisfied binding import bar with { bindings: { foo } }; ``` [16:42:28.0024] * You could even do that with module declarations and stitch them together using a `with` clause: ```js module foo { } module bar { import foo } // NOTE: not a closure, just an unsatisfied binding import bar with { bindings: { foo } }; ``` [16:42:42.0206] In my opinion, module expressions fully solve the bundling problem. [16:42:43.0277] Its definitely not as convenient [16:43:15.0070] Pardon module expressions + `Module` constructor. [16:44:05.0556] (`ModuleSource` constructor not being necessary to that end and if the committee is intransigent about `ModuleSource` providing a path to `eval`, I’m in favor of further separation of layers.) [16:44:29.0504] > <@kriskowal:matrix.org> In my opinion, module expressions fully solve the bundling problem. I assume that a pure module expression solution would do something like: ```js const foo = module {} const bar = module { import "#foo"; } // replace resolver hook to resolve "#foo" with `foo` // ... // profit? ``` [16:45:19.0146] Though, in my opinion, `ModuleSource` is no more egregious a path to `eval` than `