diff --git a/README.md b/README.md index aa40ab379..1457c0718 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/changelog.md b/changelog.md index 03f3e7c03..86b68e4b7 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,9 @@ ## Unreleased +* `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` diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 82ac431bc..ed3ede5a5 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -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 = diff --git a/locale/es-419/script.lua b/locale/es-419/script.lua index 3340f04b0..7a03ea4ba 100644 --- a/locale/es-419/script.lua +++ b/locale/es-419/script.lua @@ -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 = diff --git a/locale/ja-jp/script.lua b/locale/ja-jp/script.lua index 12fb50f65..7f125962f 100644 --- a/locale/ja-jp/script.lua +++ b/locale/ja-jp/script.lua @@ -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 = diff --git a/locale/pt-br/script.lua b/locale/pt-br/script.lua index dd1ea689d..ebde12d25 100644 --- a/locale/pt-br/script.lua +++ b/locale/pt-br/script.lua @@ -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 = diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index 2b1221ce3..a350f43fa 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -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 = @@ -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 = diff --git a/locale/zh-tw/script.lua b/locale/zh-tw/script.lua index 27613739c..cf4cd2598 100644 --- a/locale/zh-tw/script.lua +++ b/locale/zh-tw/script.lua @@ -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 = diff --git a/meta/template/basic.lua b/meta/template/basic.lua index 3050abf63..5746bc95f 100644 --- a/meta/template/basic.lua +++ b/meta/template/basic.lua @@ -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 diff --git a/script/config/template.lua b/script/config/template.lua index 150dcf50f..f3ecd8166 100644 --- a/script/config/template.lua +++ b/script/config/template.lua @@ -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) >> { diff --git a/script/core/completion/keyword.lua b/script/core/completion/keyword.lua index 1c0199a47..c390e0d11 100644 --- a/script/core/completion/keyword.lua +++ b/script/core/completion/keyword.lua @@ -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' }, diff --git a/script/library.lua b/script/library.lua index 620520dbc..b74c55f14 100644 --- a/script/library.lua +++ b/script/library.lua @@ -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 @@ -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 diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 41b0da17e..35422753a 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -79,6 +79,7 @@ local NLMap = { local LineMulti = 10000 -- goto 单独处理 +-- global 单独处理 local KeyWord = { ['and'] = true, ['break'] = true, @@ -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) @@ -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 } @@ -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 @@ -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, } @@ -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', @@ -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', @@ -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, } @@ -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 @@ -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 @@ -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 = { @@ -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 @@ -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') @@ -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', @@ -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, } @@ -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 @@ -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 @@ -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) diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 986e85e2d..d6532cfc7 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -1898,7 +1898,8 @@ local compilerSwitch = util.switch() local uri = guide.getUri(source) local version = config.get(uri, 'Lua.runtime.version') if version == 'Lua 5.3' - or version == 'Lua 5.4' then + or version == 'Lua 5.4' + or version == 'Lua 5.5' then vm.setNode(source, vm.declareGlobal('type', 'unknown')) else vm.setNode(source, vm.declareGlobal('type', 'nil')) diff --git a/script/vm/operator.lua b/script/vm/operator.lua index 07ce19ebe..cde5f8df8 100644 --- a/script/vm/operator.lua +++ b/script/vm/operator.lua @@ -362,14 +362,14 @@ vm.binarySwitch = util.switch() local uri = guide.getUri(source) local version = config.get(uri, 'Lua.runtime.version') if math.tointeger(a) and math.type(a) == 'float' then - if version == 'Lua 5.3' or version == 'Lua 5.4' then + if version == 'Lua 5.3' or version == 'Lua 5.4' or version == 'Lua 5.5' then a = ('%.1f'):format(a) else a = ('%.0f'):format(a) end end if math.tointeger(b) and math.type(b) == 'float' then - if version == 'Lua 5.3' or version == 'Lua 5.4' then + if version == 'Lua 5.3' or version == 'Lua 5.4' or version == 'Lua 5.5' then b = ('%.1f'):format(b) else b = ('%.0f'):format(b) diff --git a/test/diagnostics/readonly-for-loop-vars.lua b/test/diagnostics/readonly-for-loop-vars.lua new file mode 100644 index 000000000..8cc89cf9f --- /dev/null +++ b/test/diagnostics/readonly-for-loop-vars.lua @@ -0,0 +1,44 @@ +-- Test for readonly for-loop variables in Lua 5.5 +-- Note: These now produce parser errors instead of diagnostics + +TEST [[ +---@language Lua 5.5 +for i = 1, 10 do + i = 5 -- This is now a parser error, so no diagnostic +end +]] + +TEST [[ +---@language Lua 5.5 +for k, v in pairs(t) do + k = "new" -- This is now a parser error, so no diagnostic + v = 123 -- This is now a parser error, so no diagnostic +end +]] + +TEST [[ +---@language Lua 5.5 +for i = 1, 10 do + for j = 1, 5 do + i = j -- This is now a parser error, so no diagnostic + j = i -- This is now a parser error, so no diagnostic + end +end +]] + +-- Should not trigger for Lua 5.4 +TEST [[ +---@language Lua 5.4 +for i = 1, 10 do + i = 5 -- No error in Lua 5.4 +end +]] + +-- Should not trigger for regular locals +TEST [[ +---@language Lua 5.5 +local x = 10 +for i = 1, 10 do + x = i -- No error: x is not a for-loop variable +end +]] \ No newline at end of file