lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Sun, May 22, 2022 at 4:06 PM Philippe Verdy <verdyp@gmail.com> wrote:
Le sam. 14 mai 2022 à 04:27, Gé Weijers <ge@weijers.org> a écrit :

repeat
    if math.random() ~= 0.1 then
        goto continue
    end
    local finished = true
::continue::
until finished

Adding "continue" would not add any problem that adding "goto" did not already introduce.

That code gives unpredictable result: what is the value of the "finished" variable when jumping to the ::continue:: label from a "goto" statement where that "finished" variable was not even in scope, and so has no initial value and was still not initialized?

That "goto" statement jumps into an inner scope, so even the validity of the "goto" itself is questionable (and in my opinion it should be invalid too: "goto" is a bad statement here and I don't see any valid use of any "continue" statement or jump from inside any "repeat..until" loop, where only a "break" or jump to outside is semantically valid).

This is all well-defined in Lua, per two sections of the reference manual:

Control Structures [1]:
> In the repeatuntil loop, the inner block does not end at the until keyword, but only after the condition. [...] A label is visible in the entire block where it is defined, except inside nested functions. A goto may jump to any visible label as long as it does not enter into the scope of a local variable.[...] Labels and empty statements are called void statements, as they perform no actions.

Visibility Rules [2]:
> The scope of a local variable begins at the first statement after its declaration and lasts until the last non-void statement of the innermost block that includes the declaration.

[1]: https://www.lua.org/manual/5.4/manual.html#3.3.4
[2]: https://www.lua.org/manual/5.4/manual.html#3.5
 
Therefore the code above is not valid and is rejected by the compiler:

Lua 5.4.3  Copyright (C) 1994-2021 Lua.org, PUC-Rio
> repeat
>> if math.random() ~= 0.1 then
>> goto continue
>> end
>> local finished = true
>> ::continue::
>> until finished
stdin:7: <goto continue> at line 3 jumps into the scope of local 'finished'

However, this does work:

> for i = 1, 10 do
>> if i % 3 == 0 then
>> goto continue
>> end
>> local x = i
>> print(x)
>> ::continue::
>> end
1
2
4
5
7
8
10

...because the label is a void statement and therefore the scope of x ends with the last non-void statement, which is the print(x) statement (i.e. when the label is hit, x is already out of scope).

The following code would also be invalid for the same reason:

if math.random() ~= 0.1 then
   goto A
else
   goto B
end
local x=10
::A:: print('A', x); goto exit
local x=11
::B:: print('B', x); goto exit
::exit:: ;
Here you can get any output, not just "A 10", or "B 11", but as well "A 0", "A undefined", "A nil', "A (random string)", "B 10"... or even strange crashes caused by assertion checks made in the compiler about initialization states.

Like above, rejected by the compiler:

> do
>> if math.random() ~= 0.1 then
>> goto A
>> else
>> goto B
>> end
>> local x = 10
>> ::A:: print("A", x); goto exit
stdin:8: <goto A> at line 3 jumps into the scope of local 'x'
 
That's the major argument against "goto" which should absolutely never be used to jump to labels that are not in the current lexical scope: such use is clearly invalid (tolerated in C because local variables are allocated, but don't necessarily have to be initialized: their declaration is forward, they are "preallocated" on the stack but the initialization may have still not occured

Some C compilers enforce a initialization to binary zero for all local variables at entry of the function, even if there are later some initializers that may set (or reset) them explicitly and individually to 0.

But in Lua, this is not clear: does the compiler or interpreter have to initialize all variables in scope implicitly to zero? If so, the code above could print only "A 0" or "B 0", and explicit initializers (x=10 or x=11) would have no use, as they are not reachable, and could be safely discarded. But variables in Lua are not necessarily numbers and are polymorphic (like also in _javascript_ where all uninitialized variables have a default initialization to "undefined", which is a singleton type).

But Lua does not have a safe and well-defined singleton type for uninitialized variables, so we get unpredicatable results; and as a consequence, the "goto" statement is unsafe, it is ill-defined and this should be fixed (like in _javascript_)

There is nothing to fix and it is already well-defined, as stated above; the Lua compiler rejects gotos that jump into the scope of a local, and this rule is documented in the reference manual.

Additionally, it is also documented that goto cannot jump into a block since a label is not visible outside the block where it is defined, and this verifies when I try it:

Lua 5.4.3  Copyright (C) 1994-2021 Lua.org, PUC-Rio
> function invalidGoto()
>> goto target
>> do
>> print("unreachable")
>> ::target::
>> print("reachable if valid")
>> end
>> end
stdin:8: no visible label 'target' for <goto> at line 2
 
, maybe by explicitly stating that ALL uninitialized variables implicitly have the "nil" value, and making sure that the compiler or interpreter will allocate all variables in lexical scopes with that default value, independantly of all other initializers. Only under this condition, that "goto" statement can be safe and have predictable results, even if they jump outside from current lexical scopes.

To pre-initialize all uninitialized variables, and allow jumping into the scope of a local, the Lua compiler would have to fundamentally change. The current nature of it being a single-pass compiler, translating directly from source code to bytecode in a linear fashion, means that it can't go back and insert an initialization at the beginning of a function after several statements in that function have already been compiled. It would need to become a multi-pass compiler, translating source code to an AST or other intermediate representation and then from that to bytecode, with a pass over the intermediate representation to gather all local declarations into one place. Given that Lua is intentionally a minimalistic language that prides itself on being extremely small, I don't foresee this ever happening.