Uh oh!
There was an error while loading. Please reload this page.
repl: support top-level await - #15566
Conversation
ghost
commented
Sep 23, 2017
Can you write a small example where let and const are failing? |
benjamingr
commented
Sep 23, 2017
Great work! This is the right approach IMO. Is everything with acorn OK license wise? |
TimothyGu
commented
Sep 23, 2017
Sure. consta=awaitPromise.resolve(1);Enter a=3;Expected: Actual: |
TimothyGu
commented
Sep 23, 2017
@benjamingr Acorn uses the MIT License so our licenses are compatible. I've also added Acorn to |
addaleax
commented
Sep 23, 2017
I mean, Node’s REPL already has its problems with top-level scope (Btw, I would like it if the REPL just allowed overwriting |
TimothyGu
commented
Sep 23, 2017
That is certainly true. (e.g. #13919)
Technically yes, but I'm not convinced yet that's a good idea as it goes against the keywords' intended semantics, and what Chrome is doing. It sounds like a slippery slope. |
benjamingr
commented
Sep 23, 2017
+1 for allowing writing over const variables |
Qard
commented
Sep 23, 2017
REPL is an environment meant for experimentation. I feel like true const-ness actually doesn't really make much sense there anyway. |
Is it transformed to the following? // const a = await Promise.resolve(1);// a = 3(async()=>{void(a=awaitPromise.resolve(1))})()a=3If every I am not that experienced with this REPL project, but if this vara=awaitPromise.resolve(1);console.log(a);// 1a=3;console.log(a);// 3is transformed into this: (async()=>{void(a=awaitPromise.resolve(1))})()// console.log(a); // ReferenceError - a is not declareda=3console.log(a);// 3// setTimeout(() => {// console.log(a); // 1// }, 10);then I think we have a problem, because I'd expect this behavior: (async()=>{vara=awaitPromise.resolve(1);console.log(a);// 1a=3;console.log(a);// 3})()To make it work this way we need to change the REPL state to pending until the promise is fulfilled and continue with the next line only after that. I am not sure whether that would be an acceptable behavior. Another possible solution would be to display a message if the promise is fulfilled and you can use the results. If you choose that one, then it might be beneficial to prevent the user from setting a new value to the variable before it gets its value from the promise. Maybe I am misunderstanding something, because I don't have a testing environment to check your commit and tbh. I don't fully understand it, but this might be a harder problem than just wrapping the actual line. |
princejwesley
commented
Sep 23, 2017
@TimothyGu Behaviour is difference in canary. |
TimothyGu
commented
Sep 23, 2017
I'm sorry, I don't quite understand what you mean. The code you put in Canary's DevTools and the output you got is the exact same as what you would get from this PR. >constb=await4undefined>b4>constc=awaitPromise.resolve(1)undefined>c1>c=44>c4 |
@TimothyGu (async(self)=>{var$ret=awaitfunc();// const caseObject.defineProperty(self,'a',{get: function(){return$ret;},set: function(){thrownewTypeError('Assignment to constant variable')}});// let re-declaring case// if self has 'a', throw SyntaxErrorObject.keys(self).findIndex((key)=>key==='a')!==-1&&thrownewSyntaxError("Identifier 'a' has already been declared");// let and var caseself['a']=$ret;void0;})(global);? |
TimothyGu
commented
Sep 23, 2017
No it doesn't actually transform to that. After you enter the first line, the REPL will PAUSE until the promise returned from the async function is resolved. So when you can enter the next line, >vara=awaitPromise.resolve(1);undefined>console.log(a)1undefined>a=33>console.log(a)3undefinedAgain, I encourage y'all to actually build this version and test it out. I'm fine with answering questions, but the fastest way to answer those questions are to find out about them yourselves 😘 |
@princejwesley Wrong stuff, ignorevara=0leta=await0 |
TimothyGu
commented
Sep 23, 2017
@princejwesley |
princejwesley
commented
Sep 23, 2017
@TimothyGu I am just trying to mimic the |
TimothyGu
commented
Sep 23, 2017
@princejwesley I got you. While there are indeed some creative ways to do that, seeing how people seem to not like the restrictions of let and const at all in REPL (1/2/3) I don't think we should try to make it worse ;) |
ghost
commented
Sep 25, 2017
@TimothyGu Okay, thanks! |
cjihrig
commented
Dec 21, 2017
I'm in favor of this feature, but am also +1 on reverting until this is better sorted out. If we do decide to go the revert route, here is a PR: #17807 |
TimothyGu
commented
Dec 21, 2017
There are no REPL implications we would be “stuck” with, since any fix to REPL would be a semver-patch change. The special async mode is certainly not ideal, but the way we are operating regarding source code transformation is identical to how DevTools is; and it is my strong belief that if something is good enough for the most widely used browser in the world, it should be good enough for Node.js (not saying it couldn’t be better). I’m fine with putting this behind a flag, but given the positive developer feedback we have received so far, and the strictly additive nature of this feature (it doesn’t break any existing use cases) I am against reverting this wholesale. |
bmeck
commented
Dec 21, 2017
V8 also have an open issue on moving to a more native/feature safe without code transform to do this. Given that they are seeking to change things as well, I'm not swayed that because it is broken other places it should be ok to be broken all the places.
That seems fine, I wouldn't want it unflagged until stuff is ironed out. Some of the behavior does need to be specified like how this mode made const not const, allowed variable re-binding, and is using the global object instead of scope. |
Now that I see how this is done, it looks like this could easily be done in userland with Node 8, right? |
bmeck
commented
Dec 27, 2017
@jedwards1211 it is non-trivial and I am having to patch internals on node's repl to fix various things. I think it is unlikely to be able to be done completely in userland without something very large in scope that reimplements a large part of the repl. |
jdalton
commented
Dec 27, 2017
Go for it! I'd love to see what userland comes up with 🙌 |
jedwards1211
commented
Dec 27, 2017
@bmeck The following hack works okay in my personal scripts. Obviously it's not production quality: if(/\bawait\b/.test(cmd)&&!/\basync\b/.test(cmd)){constmatch=/^\s*var\b(.*)/.exec(cmd)if(match){cmd=`(async () => { void (${match[1]}) })()`}else{cmd=`(async () => { return ${cmd} })()`}}What part of the repl are you having to reimplement? |
@jedwards1211 if you enable such a transform to always be present repl.js most of the test suite for REPL fails. In the case of the specific above it has problems for multiple statement commands. In the case of the top level await transform that landed various things are problematic: see comment above , rewriting the |
jedwards1211
commented
Dec 28, 2017
@bmeck I'm confused why a |
@jedwards1211 there are 3 pause mechanics in repl Which is managing interrupt events from TTY for the most part. Which is managing backpressure for the most part. Which is intended to queue lines while paused, but doesn't do so if you use APIs like Edit To see more complete examples of this behavior not being fully paused in the current mechanics pipe into the repl using |
wmertens
commented
Mar 14, 2018
@jedwards1211 sorry to bother you, I'd like to use your hack, but I don't know where this code should go? |
jedwards1211
commented
Mar 14, 2018
@wmertens it goes within the |
PR-URL: #19604 Refs: #17807 Refs: #15566 (comment) Reviewed-By: Gus Caplan <me@gus.host>
PR-URL: #19604 Refs: #17807 Refs: #15566 (comment) Reviewed-By: Gus Caplan <me@gus.host>

This PR depends upon and is rebased on top of #16392.
This PR endeavors to fix#13209, following the footsteps of Google Chrome.
Algorithmically, it uses much of the same strategies as the Chromium CL -- namely, using a JS parser (Acorn) and then transforming the to-be-executed source code based on that AST. In fact, the AST node visitors are pretty much a direct port of DevTools code from DevTools' homebrew AST walker to one supplied by Acorn.
Unlike the usual parse-transform-serialize pipeline (i.e. "the Babel way"), however, I decided to adopt "the Bublé way", which is to mutate the source code directly based on the locations in the AST. While by design this approach is more fragile than a full AST serialization, it is 1) much faster, 2) requires less bundled code, 3) the approach used by Chrome DevTools, and 4) just enough for this specific use case, because we know transformed code does not need to be transformed again thereafter.
A huge focus of #13209 (and a must-have) is support for await expressions used in variable declarations and assignments. Like DevTools, this PR supports variable declarations by transforming them into assignments. Take
it is transformed to
While this works well for
var, it doesn't for top-levelletandconst(letandconststill work as expected in blocks), which will lose their lexical specialness (or constantness) with this approach. However, I simply can't think of any good way to support top-levelletandconstfully. (Note, DevTools has the same problem; tryconst a = await Promise.resolve(1);back-to-back multiple times.) If you've got an idea, please comment!The implementation in this PR is fully complete with the above caveat (though a comment or two can perhaps be added in the right places). However, tests still need to be added, especially around the SIGINT handling part, which turned out to be much more complex than the actual source transformation. For source code transformation, Chromium has two very comprehensive sets of tests that we can borrow:
Anyway, please test and report back how it works!
Many thanks to @ak239 for his DevTools implementation; wouldn't have been able to finish this without his work!
/cc @benjamingr@princejwesley
Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passesAffected core subsystem(s)
repl, readline, deps