Skip to content
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

Add Optional JavaScript 'let' variables #13851

Closed
wants to merge 5 commits into from
Closed

Add Optional JavaScript 'let' variables #13851

wants to merge 5 commits into from

Conversation

juancarlospaco
Copy link
Collaborator

@juancarlospaco juancarlospaco commented Apr 2, 2020

  • Add compile-time optional JavaScript let variable declarations.
  • https://caniuse.com/let
  • Use -d:jsLet for let variable declarations, otherwise var is used.
  • Simple code, 0 lines added, everything is done at compile-time.
  • This may not be perfect but using codegenDecl everywhere is not better.

I try compiling Karax examples and they work just Ok. NodeJs also works Ok.

We can try enabling it on Nightly or Betas, if no bug are reported for a while it can be switched to default, or just leave it as optional, is a nice option to have for Frontend people.

@ghost
Copy link

ghost commented Apr 2, 2020

Are you sure that this won't break things?
"let allows you to declare variables that are limited to a scope of a block statement, or expression on which it is used, unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope."

@Araq
Copy link
Member

Araq commented Apr 3, 2020

What's the point? JS JIT engines should have no problem optimizing var the same way they optimize let.

@timotheecour
Copy link
Member

timotheecour commented Apr 6, 2020

  • new defined symbols must start with nim prefix: jsLet => nimJsLet
    I wish we could introduce a test in CI that looks for new defined symbols in the diff and checks that

  • let is cleaner since it doesn't have global scope (less pollution etc; could be a an even bigger problem for .inject symbols? but I haven't checked whether this would introduce undesirable change in semantics

  • how about --experimental:jslet which allows localizing it

@juancarlospaco
Copy link
Collaborator Author

@Araq Problem is that is easily reversible:
JS JIT engines should have no problem optimizing let the same way they optimize var. 🤷‍♂️

var is kinda global, let is scoped, making the compiler generate better code step-by-step.

JavaScript user base is the biggest basically, attract them here may be good for Nim.

@timotheecour Maybe .inject can have something like (pseudocode):

when defined(js) and defined(nimJsLet): 
  {.warning: "JavaScript target uses 'let', injected variables are not global".}

Help prove me wrong, break it, I also try to break it, but it seems that Nim codegen is good,
and do not relies too much on mutating global variables.

@Araq
Copy link
Member

Araq commented Apr 6, 2020

Help prove me wrong, break it, I also try to break it, but it seems that Nim codegen is good,
and do not relies too much on mutating global variables.

So simply enable it all the time.

changelog.md Outdated
@@ -10,10 +10,11 @@

## Compiler changes

- Specific warnings can now be turned into errors via `--warningAsError[X]:on|off`.
- The `define` and `undef` pragmas have been de-deprecated.
- JavaScript backend adds compile-time optional `let` variable declarations,
Copy link
Member

@timotheecour timotheecour Apr 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • weirdly worded, how about:
    JavaScript backend now uses let instead of var declarations; for previous behavior, compile with -d:nimJsVar; see also https://caniuse.com/let

changelog.md Outdated
@@ -12,6 +12,9 @@

- Specific warnings can now be turned into errors via `--warningAsError[X]:on|off`.
- The `define` and `undef` pragmas have been de-deprecated.
- JavaScript backend adds compile-time optional `let` variable declarations,
compile Nim with `-d:nimJsVar` for `var` and without for `let`,
Copy link
Member

@timotheecour timotheecour Apr 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead how about using the standard flag --legacy which is exactly for this purpose;
we already have: --legacy:allowSemcheckedAstModification|checkUnsignedConversions

we'd also now have:
--legacy:jsUseVar or --legacy:jsvar

@@ -1655,15 +1655,15 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
varCode: string
varName = mangleName(p.module, v)
useReloadingGuard = sfGlobal in v.flags and p.config.hcrOn

const jsVarDecl = when defined(nimJsVar): "var" else: "let"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes no sense. Why do you need to recompile nim (aka different dialects) just to change nim's behavior?
instead either use isDefined(conf, "nimJsVar") or, better, use the suggestion i gave above with --legacy:jsUseVar

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it compile time, because either it always works or never works,
and I dont see people switching back to var if compiler uses let already.
But ok.

if v.constraint.isNil:
if useReloadingGuard:
lineF(p, "var $1;$n", varName)
if jsVarDecl: lineF(p, "var $1;$n", varName) else: lineF(p, "let $1;$n", varName)
Copy link
Member

@timotheecour timotheecour Apr 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here + elsewhere, please make it more DRY:
introduce this once: let jsdecl = if jsVarDecl: "var" else: "let"
then use it everywhere relevant eg:

lineF(p, "$1 $2;$n", [jsdecl, varName])

code repetition = WET (all acronyms of which apply)

lineF(p, "if ($1 === undefined) {$n", varName)
varCode = $varName
inc p.extraIndent
else:
varCode = "var $2"
varCode = if jsVarDecl: "var $2" else: "let $2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

varCode = jsdecl & " $2"

@@ -1673,8 +1673,12 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
if n.kind == nkEmpty:
if not isIndirect(v) and
v.typ.kind in {tyVar, tyPtr, tyLent, tyRef, tyOwned} and mapType(p, v.typ) == etyBaseIndex:
lineF(p, "var $1 = null;$n", [varName])
lineF(p, "var $1_Idx = 0;$n", [varName])
if jsVarDecl:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: no branching needed:

lineF(p, "$1 $2 = null;$n", [jsdecl, varName])
lineF(p, "$1 $2_Idx = 0;$n", [jsdecl,varName])

@@ -1702,7 +1706,11 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
else:
if targetBaseIndex:
let tmp = p.getTemp
lineF(p, "var $1 = $2, $3 = $1[0], $3_Idx = $1[1];$n",
if jsVarDecl:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

@timotheecour
Copy link
Member

timotheecour commented Apr 9, 2020

  • the failure for tests/js/tfieldchecks.nim indicates this needs more care. It's great that we have a test suite that catches this. I think the idea is sound but we need to be more careful, after fixing it please add a test showing multiple cases of interest to make sure things don't break
  • after your PR result still generates var
proc fun1(): int = result = 11
  doAssert fun1() == 11
  • after your PR some constructs (eg for loops) still generate var instead of let eg:
let a = @[1,2]
echo a
  • reduced case that fails after your PR:
when true:
  block: # works without block
    var flag = false
    proc wrap(): int =
      discard flag
      result = 12
    doAssert wrap() == 12

@timotheecour timotheecour mentioned this pull request Apr 15, 2020
1 task
@timotheecour timotheecour added the stale Staled PR/issues; remove the label after fixing them label Jun 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale Staled PR/issues; remove the label after fixing them
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants