create_collectable_thing(1);
local y = create_collectable_thing(2);
create_collectable_thing(3);
//...
return y
end
(i.e. discarding the return value of the created 1st and 3rd objects, which will still be created in the indicated order by performing the 3 function calls, because it may have other side effects even if their return value is ignored).
In this case, the 1st and 3rd object are immediately collectable after their creation and return of their creation function call, without having to first reach and starting to run the code of the return statement. So this is even more generic than just local variables as in this case above there's no local variable at all for the 1st and 3rd returned object which can still be collected earlier
Everything is know by the compiler which sees ALL the code inside the same lexical scope (there's no need to find any "end" or "return", jsut the need to determine that there's no other use before them). The compiler never compiles a partial scope line by line: this only occurs in an interactive interpreter (including in a debugger where one could modify the existing variables in scope during a debugger breakpoint) which would execute isolated statement using a permanent scope keeping all variables as it never knows if that variable will be used by a later intereactive statement.
If a debugger is used, it could load the Lua program as a script that it will compile without this optimization so that no local variables will be removed, their values will be kept; or the debugger can still load the optimized code, but will allow tracing inside the function by putting a break just after the instruction but before crossing the boundary where the scope of the varaible is lost and the object will be collected: the debugger can still inspect the returned value from the 1st call, even if it's not stored in any varaible by the traced code, but only if the breakpoint is between the 1st creation and the 2nd.
The debugger can no longer access the value of "x" later in the same block, as it is no longer accessible and its value may have been garbage collected: the local variable x for example is visible by the debugger, even if it is allocated nowhere and nothig is stored by any instruction in the bytecode. to do that, the debugger creates an internal reference of the return value at the position of the breakpoint, so the garbage collector will not collect it (as the garbage collector will see that the value is still referenced, may be not inside the traced code, but in some object of the debugger).
Typically, the debugger will control the garbage collector to instruct it to not collect any object that the debugger is bookkeeping (under instruction by the user of the debugger).