What does the identifier between the local keyword and variable name do?

I found out that this is a valid Luau syntax.

local a b: c, d: e, f: g

I had a quick look at the grammar and this does not seem to be included at it only accounts for bindinglist:

'local' bindinglist ['=' explist]

binding = NAME [':' TypeAnnotation]
bindinglist = binding [',' bindinglist] (* equivalent of Lua 5.1 'namelist', except with optional type annotations *)

…and then I decided to quickly look at the source code of Luau AST, Parser::parseLocal function is Parser.cpp, and nothing about this seems to be coded here.

matchRecoveryStopOnToken['=']++;

        TempVector<Binding> names(scratchBinding);
        parseBindingList(names);

        matchRecoveryStopOnToken['=']--;

        TempVector<AstLocal*> vars(scratchLocal);

        TempVector<AstExpr*> values(scratchExpr);

        std::optional<Location> equalsSignLocation;

        if (lexer.current().type == '=')
        {

My question is, what does the identifer between the local and the first variable name do? e.g. local identifier varname, local foo bar: Type, baz: number.

Apparently local xnil xnil = nil makes the table clear loop go faster but that doen’t explain why other identifiers are also allowed. e.g. local this_also_works nil =nil.

Is other identifiers aside from xnil made allowed to be a valid syntax by accident?
Starting to think about it, does other identifiers between local keyword and the variable name even do anything?

From what I’ve tried, its a bug with the analyzer. luau-lang throws a syntax error from the first code example

The demo might not take typings into account however.

local a a = nil works since you’re actually writing two statements here, not one

2 Likes

For the local xnil xnil = nil case, it seems like that’s just making an empty variable which is implicitly set to nil, and then manually setting it to nil after. I’m not sure why that would make anything go faster as loading a nil value LOADNIL wouldn’t seem to be any slower than copying a value MOVE.

It’s not, it’s a syntax error.

It’s a little complex/specific to that case, the performance difference is due to LOADNIL being emitted inside the loop vs outside the loop essentially:

    4: for k,v in t do
MOVE R1 R0
LOADNIL R2
LOADNIL R3
FORGPREP R1 L1
    5:  t[k] = nil
L0: LOADNIL R6
SETTABLE R6 R0 R4
    4: for k,v in t do
L1: FORGLOOP R1 L0 2

vs

    8: local xnil xnil = nil
LOADNIL R1
LOADNIL R1
   10: for k,v in t do
MOVE R2 R0
LOADNIL R3
LOADNIL R4
FORGPREP R2 L3
   11:  t[k] = xnil
L2: SETTABLE R1 R0 R5
   10: for k,v in t do
L3: FORGLOOP R2 L2 2
4 Likes