Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The Lua language server provides various language features for Lua to make devel

## Features

- ⚙️ Supports `Lua 5.4`, `Lua 5.3`, `Lua 5.2`, `Lua 5.1`, and `LuaJIT`
- ⚙️ Supports `Lua 5.5`, `Lua 5.4`, `Lua 5.3`, `Lua 5.2`, `Lua 5.1`, and `LuaJIT`
- 📄 Over 20 supported [annotations](https://luals.github.io/wiki/annotations/) for documenting your code
- ↪ Go to definition
- 🦺 Dynamic [type checking](https://luals.github.io/wiki/type-checking/)
Expand Down
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased
<!-- Add all new changes here. They will be moved under a version at release -->
* `NEW` Add support for Lua 5.5 runtime version
* `NEW` Support `global` keyword syntax for Lua 5.5
* `NEW` Add diagnostic for read-only for-loop variables in Lua 5.5

## 3.15.0
`2025-6-25`
Expand Down
2 changes: 2 additions & 0 deletions locale/en-us/script.lua
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ PARSER_MISS_SEP_IN_TABLE =
'Miss symbol `,` or `;` .'
PARSER_SET_CONST =
'Assignment to const variable.'
PARSER_SET_FOR_LOOP_VAR =
'Cannot assign to for-loop variable `{}` (read-only in Lua 5.5).'
PARSER_UNICODE_NAME =
'Contains Unicode characters.'
PARSER_ERR_NONSTANDARD_SYMBOL =
Expand Down
2 changes: 2 additions & 0 deletions locale/es-419/script.lua
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ PARSER_MISS_SEP_IN_TABLE =
'Falta el símbolo `,` ó `;` .'
PARSER_SET_CONST =
'Asignación de valor a una variable constante.'
PARSER_SET_FOR_LOOP_VAR =
'No se puede asignar a la variable de bucle for `{}` (solo lectura en Lua 5.5).'
PARSER_UNICODE_NAME =
'Contiene caracteres Unicode.'
PARSER_ERR_NONSTANDARD_SYMBOL =
Expand Down
2 changes: 2 additions & 0 deletions locale/ja-jp/script.lua
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ PARSER_MISS_SEP_IN_TABLE =
'区切りには `,` または `;` が必要です。'
PARSER_SET_CONST =
'定数に値を代入できません。'
PARSER_SET_FOR_LOOP_VAR =
'forループ変数`{}`に代入できません(Lua 5.5では読み取り専用)。'
PARSER_UNICODE_NAME =
'Unicode 文字が含まれています。'
PARSER_ERR_NONSTANDARD_SYMBOL =
Expand Down
2 changes: 2 additions & 0 deletions locale/pt-br/script.lua
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ PARSER_MISS_SEP_IN_TABLE =
'Falta o símbolo `,` ou `;` .'
PARSER_SET_CONST =
'Atribuição à variável constante.'
PARSER_SET_FOR_LOOP_VAR =
'Não é possível atribuir à variável de loop for `{}` (somente leitura no Lua 5.5).'
PARSER_UNICODE_NAME =
'Contém caracteres Unicode.'
PARSER_ERR_NONSTANDARD_SYMBOL =
Expand Down
4 changes: 4 additions & 0 deletions locale/zh-cn/script.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ DIAG_UNUSED_VARARG =
'未使用的不定参数。'
DIAG_REDEFINED_LOCAL =
'重定义局部变量 `{}`。'
DIAG_READONLY_FOR_LOOP_VAR =
'无法给 for 循环变量 `{}` 赋值(在 Lua 5.5 中为只读)。'
DIAG_DUPLICATE_INDEX =
'重复的索引 `{}`。'
DIAG_DUPLICATE_METHOD =
Expand Down Expand Up @@ -293,6 +295,8 @@ PARSER_MISS_SEP_IN_TABLE =
'需要用`,`或`;`进行分割。'
PARSER_SET_CONST =
'不能对常量赋值。'
PARSER_SET_FOR_LOOP_VAR =
'不能对for循环变量`{}`赋值(在Lua 5.5中只读)。'
PARSER_UNICODE_NAME =
'包含了 Unicode 字符。'
PARSER_ERR_NONSTANDARD_SYMBOL =
Expand Down
2 changes: 2 additions & 0 deletions locale/zh-tw/script.lua
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ PARSER_MISS_SEP_IN_TABLE =
'需要用 `,` 或 `;` 進行分割。'
PARSER_SET_CONST =
'不能對常數賦值。'
PARSER_SET_FOR_LOOP_VAR =
'不能對for迴圈變數`{}`賦值(在Lua 5.5中唯讀)。'
PARSER_UNICODE_NAME =
'包含了 Unicode 字元。'
PARSER_ERR_NONSTANDARD_SYMBOL =
Expand Down
2 changes: 2 additions & 0 deletions meta/template/basic.lua
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ _VERSION = "Lua 5.2"
_VERSION = "Lua 5.3"
---#elseif VERSION == 5.4 then
_VERSION = "Lua 5.4"
---#elseif VERSION == 5.5 then
_VERSION = "Lua 5.5"
---#end

---@version >5.4
Expand Down
1 change: 1 addition & 0 deletions script/config/template.lua
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ local template = {
'Lua 5.2',
'Lua 5.3',
'Lua 5.4',
'Lua 5.5',
'LuaJIT',
},
['Lua.runtime.path'] = Type.Array(Type.String) >> {
Expand Down
8 changes: 8 additions & 0 deletions script/core/completion/keyword.lua
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,14 @@ end"
end
return false
end },
{ 'global', function(info, results)
local version = config.get(info.uri, 'Lua.runtime.version')
if version ~= 'Lua 5.5' then
return false
end
-- Note: No special completion for 'global function' syntax as it doesn't exist in Lua 5.5
return false
end },
{ 'nil' },
{ 'not' },
{ 'or' },
Expand Down
4 changes: 4 additions & 0 deletions script/library.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ local function getDocFormater(uri)
return 'HOVER_NATIVE_DOCUMENT_LUA53'
elseif version == 'Lua 5.4' then
return 'HOVER_NATIVE_DOCUMENT_LUA54'
elseif version == 'Lua 5.5' then
return 'HOVER_NATIVE_DOCUMENT_LUA54' -- Use 5.4 docs for 5.5 until 5.5 specific docs are available
elseif version == 'LuaJIT' then
return 'HOVER_NATIVE_DOCUMENT_LUAJIT'
end
Expand All @@ -43,6 +45,8 @@ local function getDocFormater(uri)
return 'HOVER_DOCUMENT_LUA53'
elseif version == 'Lua 5.4' then
return 'HOVER_DOCUMENT_LUA54'
elseif version == 'Lua 5.5' then
return 'HOVER_DOCUMENT_LUA54' -- Use 5.4 docs for 5.5 until 5.5 specific docs are available
elseif version == 'LuaJIT' then
return 'HOVER_DOCUMENT_LUAJIT'
end
Expand Down
125 changes: 113 additions & 12 deletions script/parser/compile.lua
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ local NLMap = {
local LineMulti = 10000

-- goto 单独处理
-- global 单独处理
local KeyWord = {
['and'] = true,
['break'] = true,
Expand Down Expand Up @@ -262,6 +263,36 @@ local function addSpecial(name, obj)
obj.special = name
end

---@param local parser.object
---@return boolean
local function isForLoopVariable(local_)
-- Check if this local is a for-loop variable
if not local_ or local_.type ~= 'local' then
return false
end

local parent = local_.parent
if not parent then
return false
end

-- Check if parent is a numeric for-loop
if parent.type == 'loop' and parent.loc == local_ then
return true
end

-- Check if parent is a for-in loop
if parent.type == 'in' and parent.keys then
for i = 1, #parent.keys do
if parent.keys[i] == local_ then
return true
end
end
end

return false
end

---@param offset integer
---@param leftOrRight '"left"'|'"right"'
local function getPosition(offset, leftOrRight)
Expand Down Expand Up @@ -706,12 +737,12 @@ local function parseLocalAttrs()
else
missSymbol '>'
end
if State.version ~= 'Lua 5.4' then
if State.version ~= 'Lua 5.4' and State.version ~= 'Lua 5.5' then
pushError {
type = 'UNSUPPORT_SYMBOL',
start = attr.start,
finish = attr.finish,
version = 'Lua 5.4',
version = {'Lua 5.4', 'Lua 5.5'},
info = {
version = State.version
}
Expand Down Expand Up @@ -752,6 +783,19 @@ local function createLocal(obj, attrs)
return obj
end

---@param obj table
local function createGlobal(obj, attrs)
obj.type = 'setglobal'
obj.effect = obj.finish

if attrs then
obj.attrs = attrs
attrs.parent = obj
end

return obj
end

local function pushChunk(chunk)
Chunk[#Chunk+1] = chunk
end
Expand Down Expand Up @@ -906,13 +950,14 @@ local function parseStringUnicode()
end
if State.version ~= 'Lua 5.3'
and State.version ~= 'Lua 5.4'
and State.version ~= 'Lua 5.5'
and State.version ~= 'LuaJIT'
then
pushError {
type = 'ERR_ESC',
start = leftPos - 2,
finish = rightPos,
version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
version = {'Lua 5.3', 'Lua 5.4', 'Lua 5.5', 'LuaJIT'},
info = {
version = State.version,
}
Expand All @@ -932,7 +977,7 @@ local function parseStringUnicode()
end
return nil, offset
end
if State.version == 'Lua 5.4' then
if State.version == 'Lua 5.4' or State.version == 'Lua 5.5' then
if byte < 0 or byte > 0x7FFFFFFF then
pushError {
type = 'UTF8_MAX',
Expand All @@ -951,7 +996,7 @@ local function parseStringUnicode()
type = 'UTF8_MAX',
start = leftPos,
finish = rightPos,
version = byte <= 0x7FFFFFFF and 'Lua 5.4' or nil,
version = (byte <= 0x7FFFFFFF and {'Lua 5.4', 'Lua 5.5'}) or nil,
info = {
min = '000000',
max = '10FFFF',
Expand Down Expand Up @@ -1095,7 +1140,7 @@ local function parseShortString()
type = 'ERR_ESC',
start = left,
finish = left + 4,
version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'Lua 5.5', 'LuaJIT'},
info = {
version = State.version,
}
Expand Down Expand Up @@ -1274,7 +1319,7 @@ local function parseNumber2(start)
finish = getPosition(offset - 1, 'right'),
version = 'LuaJIT',
info = {
version = 'Lua 5.4',
version = {'Lua 5.4', 'Lua 5.5'},
}
}
end
Expand Down Expand Up @@ -1409,6 +1454,18 @@ local function isKeyWord(word, nextToken)
end
return true
end
if word == 'global' then
if State.version ~= 'Lua 5.5' then
return false
end
if not nextToken then
return false
end
if CharMapWord[ssub(nextToken, 1, 1)] then
return true
end
return false
end
return false
end

Expand Down Expand Up @@ -2673,10 +2730,11 @@ local function parseBinaryOP(asAction, level)
or token == '<<'
or token == '>>' then
if State.version ~= 'Lua 5.3'
and State.version ~= 'Lua 5.4' then
and State.version ~= 'Lua 5.4'
and State.version ~= 'Lua 5.5' then
pushError {
type = 'UNSUPPORT_SYMBOL',
version = {'Lua 5.3', 'Lua 5.4'},
version = {'Lua 5.3', 'Lua 5.4', 'Lua 5.5'},
start = op.start,
finish = op.finish,
info = {
Expand Down Expand Up @@ -2893,6 +2951,15 @@ local function bindValue(n, v, index, lastValue, isLocal, isSet)
start = n.start,
finish = n.finish,
}
elseif State.version == 'Lua 5.5' and isForLoopVariable(loc) then
pushError {
type = 'SET_FOR_LOOP_VAR',
start = n.start,
finish = n.finish,
info = {
name = loc[1],
},
}
end
end
end
Expand Down Expand Up @@ -3119,6 +3186,27 @@ local function parseLocal()
return loc
end

local function parseGlobal()
local globalPos = getPosition(Tokens[Index], 'left')

Index = Index + 2
skipSpace()

local name = parseName(true)
if not name then
missName()
return nil
end
local glob = createGlobal(name)
glob.globPos = globalPos
glob.effect = maxinteger
pushActionIntoCurrentChunk(glob)
skipSpace()
parseMultiVars(glob, parseName, false)

return glob
end

local function parseDo()
local doLeft = getPosition(Tokens[Index], 'left')
local doRight = getPosition(Tokens[Index] + 1, 'right')
Expand Down Expand Up @@ -3229,7 +3317,7 @@ local function parseLabel()
local name = label[1]
local olabel = guide.getLabel(block, name)
if olabel then
if State.version == 'Lua 5.4'
if (State.version == 'Lua 5.4' or State.version == 'Lua 5.5')
or block == guide.getBlock(olabel) then
pushError {
type = 'REDEFINED_LABEL',
Expand All @@ -3252,7 +3340,7 @@ local function parseLabel()
type = 'UNSUPPORT_SYMBOL',
start = left,
finish = lastRightPosition(),
version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'Lua 5.5', 'LuaJIT'},
info = {
version = State.version,
}
Expand Down Expand Up @@ -3634,7 +3722,7 @@ local function parseFor()
missExp()
end

if State.version == 'Lua 5.4' then
if State.version == 'Lua 5.4' or State.version == 'Lua 5.5' then
forStateVars = 4
else
forStateVars = 3
Expand Down Expand Up @@ -3902,6 +3990,10 @@ function parseAction()
return parseLocal()
end

if token == 'global' and isKeyWord('global', Tokens[Index + 3]) then
return parseGlobal()
end

if token == 'if'
or token == 'elseif'
or token == 'else' then
Expand Down Expand Up @@ -3958,6 +4050,15 @@ function parseAction()
start = name.start,
finish = name.finish,
}
elseif State.version == 'Lua 5.5' and isForLoopVariable(loc) then
pushError {
type = 'SET_FOR_LOOP_VAR',
start = name.start,
finish = name.finish,
info = {
name = loc[1],
},
}
end
end
pushActionIntoCurrentChunk(name)
Expand Down
Loading