Skip to content

'local'-keyword has unexpected behavior #194

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

Open
SkyyySi opened this issue Mar 18, 2025 · 10 comments
Open

'local'-keyword has unexpected behavior #194

SkyyySi opened this issue Mar 18, 2025 · 10 comments

Comments

@SkyyySi
Copy link

SkyyySi commented Mar 18, 2025

Currently, local behaves in an unexpected way. For example, this YueScript code ...

local x = "y"

... compiles to the following Lua code:

local x
x = "y"

I would appreciate it if this was changed to behave the same as it does in Lua. If someone is going out of their way to explicitly declare the variable in the same statement, then there's likely a reason for it. If someone really wanted to pre-declare the variable, they could just do that instead.

pigpigyyy added a commit that referenced this issue Mar 19, 2025
@pigpigyyy
Copy link
Member

pigpigyyy commented Mar 19, 2025

This should be partially addressed by commit 28bae65. Currently, only specific cases of assignments are transpiled into the expected one-liner form.

For example, the following cases are handled correctly:

-- supported cases
local x = 1
local y, z = "sds", false
local n, m = func!

Which transpile to:

local x = 1
local y, z = "sds", false
local n, m = func()

However, there are still cases where this behavior is not yet supported, such as:

-- unsupported cases
local a = tb?\func!
local b = item\end 123
local c = if true then 1

These are transpiled into the following Lua code:

local a
do
  local _obj_0 = tb
  if _obj_0 ~= nil then
    a = _obj_0:func()
  end
end

local b
local _call_0 = item
b = _call_0["end"](_call_0, 123)

local c
if true then
  c = 1
end

In these unsupported cases, the variable declaration and assignment are still split due to the complexity of the expressions.

@SkyyySi
Copy link
Author

SkyyySi commented Mar 19, 2025

This is a sensible solution. However, for the sake of consistency, how about doing the following:

Given this YueScript code:

local x = if x then "Yes" else "No"

Generate this:

local x = x
if x then
    x = "Yes"
else
    x = "No"
end

The compiler could observe if the expression that's being assigned to x is itself mentioning x, and if it does (like the if x then ... above), it inserts local x = x instead of just local x.

@johnd0e
Copy link

johnd0e commented Mar 19, 2025

local b = item\end 123

Still not clear why this is unsupported, and why it ever needs extra _call_0 variable.

@pigpigyyy
Copy link
Member

local b = item\end 123

Still not clear why this is unsupported, and why it ever needs extra _call_0 variable.

In this code, the compiler interprets item as a global variable. Accessing a global variable can have side effects, so the extra _call_0 is introduced to ensure the access happens only once.

So that the following code should be handled as one-liner, while it is not yet done.

item = {}
local b = item\end 123

@pigpigyyy
Copy link
Member

@SkyyySi local x = x is not acceptable because the x in the right may also trigger a unintended global accessing. If you happen to alter a global environment with some like this, a function will be called unintentionally.

-- You can try the code in YueScript 0.27.1
_ENV = <index>: (key) => print "accessing a global named #{key}"
local x = x

@SkyyySi
Copy link
Author

SkyyySi commented Mar 19, 2025

But if your expression explicitly asks to accesses a variable x (by using its name somewhere), how could it be an accidental global read? The only cases in which a programmer didn't mean to do that are:

  1. The code is bugged and wasn't meant to reference x in the first place.
  2. They meant to just use nil instead of x (since, right now, x can only ever be nil before the expression is completely evaluated and assigned to x).

I don't think either of those are compelling arguments.


There are also genuine reasons why someone might want this to happen. Primarily, that would be caching global variables. It's a common thing in Lua to write something like this:

local assert = assert
local pcall = pcall
local string = string
--- You get the idea

Besides that: If someone actually does something as horrible as triggering side-effects whenever the global environment is accessed, then they deserve whatever happens to them ;)

@vendethiel
Copy link

I think the argument is that you wouldn't expect the side-effect twice when you only see it once in your code. YueScript caches it so the side effect is only executed once.

@SkyyySi
Copy link
Author

SkyyySi commented Mar 19, 2025

I think the argument is that you wouldn't expect the side-effect twice when you only see it once in your code. YueScript caches it so the side effect is only executed once.

  • The side effect isn't triggered twice when doing local x = x in Lua. It runs once, as one would expect.
  • It currently doesn't get trigged at all in YueScript, since it is compiling to
    local x
    x = x
  • YueScript doesn't do any caching in this recard. It does pre-declare the variable, but that's also the very thing that I was hoping to see changed when opening this issue.

@vendethiel
Copy link

I know how assignments work. I'm talking about a case where the variable gets cached, like the case posted earlier in this very thread:

local b
local _call_0 = item
b = _call_0["end"](_call_0, 123)

As long as it's compiled this way, it has to cache item to avoid double side effects

@SkyyySi
Copy link
Author

SkyyySi commented Mar 19, 2025

In those case, falling back to an IIFE is always an option:

local b = ((function(_call_0)
    return _call_0["end"](_call_0, 123)
end)(item))

Or, alternatively, an additional temporary variable can be generated by the compiler:

local _b_temp_0
local _call_0 = item
_b_temp_0 = _call_0["end"](_call_0, 123)
local b = _b_temp_0

Both would preserve the intended behaviour with function environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants