-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhancement: Output let and const for variable declarations, without breaking changes #5377
Comments
I am looking at implementing this as it helps with TypeScript output (#5307). In TypeScript, However, I am not convinced that there's significant benefit to automatically outputting So I would make almost the opposite proposal: keep using Here's an example to think about: if doingWell()
status = 'OK'
else
status = 'bad' I'd propose that this outputs the following: if (doingWell()) {
var feeling = 'OK';
} else {
feeling = 'bad';
} In TypeScript, we get The code above looks a little weird to humans, but I think only because we intuitively think of The one case where we can't add |
The intent of the original proposal was to have CoffeeScript output more idiomatic modern ES code, where people generally use only
They may be allowed, but they’re strongly discouraged as they look like a bug, like the author forgot that a variable had already been declared. Banning repeated declarations is one of eslint’s core recommended rules. It also violates the CoffeeScript principle that our JavaScript output should be human readable.
I sympathize with this goal. How about as a first step you simply try to get
This alone would be an improvement over the current output, regardless of anything related to static types; and it would get you a long way toward your goal of output JavaScript that can be typed implicitly. Once this is achieved, the only question would be how to handle the cases written using To go back to your const status = doingWell() ?
'OK' :
'bad'; I feel like I understand why people do this. I get the desire to use status = if doingWell()
'OK'
else
'bad' And this would/should output as a ternary which could use |
I'm pretty sure even I think switching to a two-pass compiler would be a major undertaking. (In fact, I just tried to do it, and ran into all sorts of trouble because so much of what we currently do in I don't see how this extreme level of change makes sense for making output slightly more idiomatic/nice in modern ECMAScript. Even TypeScript supports To your other question, there is lots of code that isn't amenable to any = false
all = true
squares = []
for x in list
any or= x
all and= x
squares.push x ** 2 Sure, this could be done with By contrast, in a single pass, my current branch generates the following code, which feels pretty idiomatic to me (but with var any = false; // : boolean
var all = true; // : boolean
var squares = []; // : any[]
for (var i = 0, len = list.length; i < len; i++) {
var x = list[i];
any || (any = x);
all && (all = x);
squares.push(x ** 2);
} Note that In fact, TypeScript + any for loop is a good argument for for (i = 0, len = list.length; i < len; i++) {
var x = list[i];
f(x);
} By contrast, we can't use |
I thought there was already something similar to the “two pass” approach happening in order to output the Updating CoffeeScript to understand block scopes in addition to function scopes would be a significant refactor, yes, though one that I think wouldn’t be bad to do on its own merits; but it’s only necessary if we want to output the |
I guess the line between one and two passes is a fuzzy one. By "pass" I meant one depth-first traversal of the parse tree. Currently,
And
Note that, by the time By contrast, detecting that |
Sure, we can at least discuss that on its PR (or a new discussion issue). Improving type detection (the |
A related sub-issue could be to start using for a in [1..b]
console.log a for (let i = 1, a = i, ref = b; (1 <= ref ? i <= ref : i >= ref); a = 1 <= ref ? ++i : --i) {
console.log(a);
} This will keep inaccessible refs confined to as narrow a scope as possible and will reduce the linear growth of ref vars in the whole file. |
|
@Inve1951 Good point, it should end up like: var a;
for (let i = a = 1, ref = b; (1 <= ref ? i <= ref : i >= ref); a = 1 <= ref ? ++i : --i) {
console.log(a);
} |
I was thinking in the context of JSDoc type annotations here (#5307 (comment)), and realized one important use case for both type annotations and # you can do this now:
f1 = (x) -> x
# as a top-level assignment, you can employ the `!<...>` constraint expression:
f2!<const> = (x) -> x This enables more precise control over the exported ABI: var f1;
f1 = function(x) {
return x;
}
const f2 = function(x) {
return x;
} |
I have introduced a non-breaking change which makes CoffeeScript aware of block scope: #5475. This does not introduce or change any syntax, which I think is very much aligned with the spirit of this issue: introducing block-scoped declarations for the user, in the spirit of CoffeeScript. Please take a look if interested! |
I also specifically want to respond to this comment:
"huge modification to the compiler" is incredibly strong phrasing, and it's also patently incorrect: I was able to add block-level scope awareness in #5475 after a week of noodling around. I didn't add I'm not sure how to achieve this issue's goal yet, since I'm currently focused on how to do the opposite: explicit |
Migrating this from #5344 (comment):
Now that CoffeeScript 2 outputs modern ES6+ syntax, we can use
let
andconst
in our output. Leaving aside the question of supporting block-scoped variables in the CoffeeScript input, (see #4985) there’s no reason that our generated output needs to usevar
when it could uselet
orconst
instead. We could therefore make two improvements in output code readability:Whenever possible, variable declarations should be placed at the block scope (via
let
) rather than always at the top of the function scope (the current behavior withvar
).Whenever possible, variable declarations should use
const
. This would be whenever a variable is never reassigned.The key here is to not cause any breaking changes. So when in doubt, we would keep the current “define at the top of the function scope” behavior. A declaration/first assignment within a loop is a example of such a case. A
let x
within a loop means thatx
is declared multiple times (once for each iteration of the loop) which might be a breaking change for existing code.If you look at this example:
breakfast
is only used within thatif
block, but it’s currently getting avar breakfast;
line at the top of the output. The output instead could be this:And then phase 2 would see that
breakfast
is never reassigned and never referenced before its assignment (read about thelet
/const
“temporal dead zone”) and useconst
instead:I would try to achieve the two enhancements as separate PRs, probably with the block-scoping first.
The text was updated successfully, but these errors were encountered: