From 2ea6169cbdd103e0da02b0fc07624d79c5e9afe9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:00:06 +0000 Subject: [PATCH 1/9] Initial plan From 17d35b83f59aab82016170677c449eefb72ab839 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:15:17 +0000 Subject: [PATCH 2/9] Add Lua 5.5 support to lua-language-server Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- README.md | 2 +- meta/template/basic.lua | 2 ++ script/config/template.lua | 1 + script/library.lua | 4 ++++ script/parser/compile.lua | 26 ++++++++++++++------------ script/vm/compiler.lua | 3 ++- script/vm/operator.lua | 4 ++-- 7 files changed, 26 insertions(+), 16 deletions(-) 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/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/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..5891dcbf7 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -706,12 +706,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 } @@ -906,13 +906,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 +933,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 +952,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 +1096,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 +1275,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 @@ -2673,10 +2674,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 = { @@ -3229,7 +3231,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 +3254,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 +3636,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 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) From 15c897a6b43a294b83d6895c98601f16dd6d60b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:35:15 +0000 Subject: [PATCH 3/9] Add global keyword support for Lua 5.5 Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- script/core/completion/keyword.lua | 28 +++++++++++ script/parser/compile.lua | 78 ++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/script/core/completion/keyword.lua b/script/core/completion/keyword.lua index 1c0199a47..89b90a73d 100644 --- a/script/core/completion/keyword.lua +++ b/script/core/completion/keyword.lua @@ -232,6 +232,34 @@ end" insertText = "\z local function $1($2)\ \t$0\ +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 + if info.hasSpace then + results[#results+1] = { + label = 'global function', + kind = define.CompletionItemKind.Snippet, + insertTextFormat = 2, + insertText = "\z +function $1($2)\ +\t$0\ +end" + } + else + results[#results+1] = { + label = 'global function', + kind = define.CompletionItemKind.Snippet, + insertTextFormat = 2, + insertText = "\z +global function $1($2)\ +\t$0\ end" } end diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 5891dcbf7..95b594f8b 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -89,6 +89,7 @@ local KeyWord = { ['false'] = true, ['for'] = true, ['function'] = true, + ['global'] = true, ['if'] = true, ['in'] = true, ['local'] = true, @@ -208,6 +209,7 @@ local ChunkStartMap = { ['elseif'] = true, ['for'] = true, ['function'] = true, + ['global'] = true, ['if'] = true, ['local'] = true, ['repeat'] = true, @@ -752,6 +754,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 @@ -3121,6 +3136,65 @@ local function parseLocal() return loc end +local function parseGlobal() + local globalPos = getPosition(Tokens[Index], 'left') + + -- Global declarations are only supported in Lua 5.5 + if State.version ~= 'Lua 5.5' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = globalPos, + finish = getPosition(Tokens[Index] + 5, 'right'), + version = 'Lua 5.5', + info = { + version = State.version, + } + } + end + + Index = Index + 2 + skipSpace() + local word = peekWord() + if not word then + missName() + return nil + end + + if word == 'function' then + local func = parseFunction(false, true) + local name = func.name + if name then + func.name = nil + name.type = GetToSetMap[name.type] + name.value = func + name.vstart = func.start + name.range = func.finish + name.globPos = globalPos + func.parent = name + pushActionIntoCurrentChunk(name) + return name + else + missName(func.keyword[2]) + pushActionIntoCurrentChunk(func) + return func + end + end + + 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') @@ -3904,6 +3978,10 @@ function parseAction() return parseLocal() end + if token == 'global' then + return parseGlobal() + end + if token == 'if' or token == 'elseif' or token == 'else' then From aa06ff55bccaa7c6d0b6a927c56bff917bf3cc5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:52:57 +0000 Subject: [PATCH 4/9] Add readonly for-loop variables diagnostic for Lua 5.5 Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- locale/en-us/script.lua | 2 + locale/zh-cn/script.lua | 2 + .../diagnostics/readonly-for-loop-vars.lua | 67 +++++++++++++++++++ script/proto/diagnostic.lua | 8 +++ test/diagnostics/readonly-for-loop-vars.lua | 43 ++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 script/core/diagnostics/readonly-for-loop-vars.lua create mode 100644 test/diagnostics/readonly-for-loop-vars.lua diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 82ac431bc..6f48fa2b4 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -24,6 +24,8 @@ DIAG_UNUSED_VARARG = 'Unused vararg.' DIAG_REDEFINED_LOCAL = 'Redefined local `{}`.' +DIAG_READONLY_FOR_LOOP_VAR = +'Cannot assign to for-loop variable `{}` (read-only in Lua 5.5).' DIAG_DUPLICATE_INDEX = 'Duplicate index `{}`.' DIAG_DUPLICATE_METHOD = diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index 2b1221ce3..8d0450042 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 = diff --git a/script/core/diagnostics/readonly-for-loop-vars.lua b/script/core/diagnostics/readonly-for-loop-vars.lua new file mode 100644 index 000000000..c5a7caea8 --- /dev/null +++ b/script/core/diagnostics/readonly-for-loop-vars.lua @@ -0,0 +1,67 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' +local await = require 'await' + +---@param source parser.object +---@return boolean +local function isForLoopVariable(source) + -- Check if this local is a for-loop variable + if source.type ~= 'local' then + return false + end + + local parent = source.parent + if not parent then + return false + end + + -- Check if parent is a numeric for-loop + if parent.type == 'loop' and parent.loc == source 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] == source then + return true + end + end + end + + return false +end + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + -- Only check for Lua 5.5 + if state.version ~= 'Lua 5.5' then + return + end + + ---@async + guide.eachSourceType(state.ast, 'setlocal', function (source) + await.delay() + + -- Get the original local declaration + local node = source.node + if not node then + return + end + + -- Check if this local is a for-loop variable + if isForLoopVariable(node) then + callback { + start = source.start, + finish = source.finish, + message = lang.script('DIAG_READONLY_FOR_LOOP_VAR', node[1]), + } + end + end) +end \ No newline at end of file diff --git a/script/proto/diagnostic.lua b/script/proto/diagnostic.lua index 72761493e..1a07c8667 100644 --- a/script/proto/diagnostic.lua +++ b/script/proto/diagnostic.lua @@ -174,6 +174,14 @@ m.register { status = 'Opened', } +m.register { + 'readonly-for-loop-vars', +} { + group = 'readonly', + severity = 'Error', + status = 'Opened', +} + m.register { 'undefined-global', 'global-in-nil-env', diff --git a/test/diagnostics/readonly-for-loop-vars.lua b/test/diagnostics/readonly-for-loop-vars.lua new file mode 100644 index 000000000..e9101ea99 --- /dev/null +++ b/test/diagnostics/readonly-for-loop-vars.lua @@ -0,0 +1,43 @@ +-- Test for readonly for-loop variables in Lua 5.5 + +TEST [[ +---@language Lua 5.5 +for i = 1, 10 do + = 5 -- Error: Cannot assign to for-loop variable +end +]] + +TEST [[ +---@language Lua 5.5 +for k, v in pairs(t) do + = "new" -- Error: Cannot assign to for-loop variable + = 123 -- Error: Cannot assign to for-loop variable +end +]] + +TEST [[ +---@language Lua 5.5 +for i = 1, 10 do + for j = 1, 5 do + = j -- Error: Cannot assign to outer for-loop variable + = i -- Error: Cannot assign to inner for-loop variable + 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 From cd54310b7d612cb031cbaf20f7b43c6234b6ec34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:54:45 +0000 Subject: [PATCH 5/9] Fix version check to use config system in readonly for-loop diagnostic Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- script/core/diagnostics/readonly-for-loop-vars.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/core/diagnostics/readonly-for-loop-vars.lua b/script/core/diagnostics/readonly-for-loop-vars.lua index c5a7caea8..465994599 100644 --- a/script/core/diagnostics/readonly-for-loop-vars.lua +++ b/script/core/diagnostics/readonly-for-loop-vars.lua @@ -2,6 +2,7 @@ local files = require 'files' local guide = require 'parser.guide' local lang = require 'language' local await = require 'await' +local config = require 'config' ---@param source parser.object ---@return boolean @@ -41,7 +42,8 @@ return function (uri, callback) end -- Only check for Lua 5.5 - if state.version ~= 'Lua 5.5' then + local version = config.get(uri, 'Lua.runtime.version') + if version ~= 'Lua 5.5' then return end From aa69c2f9f939f418c71d08c7607e42e7f0e921ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:05:12 +0000 Subject: [PATCH 6/9] Fix global parsing to fallback when not Lua 5.5 and remove non-existent global function syntax Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- script/core/completion/keyword.lua | 22 +----------------- script/parser/compile.lua | 36 ++---------------------------- 2 files changed, 3 insertions(+), 55 deletions(-) diff --git a/script/core/completion/keyword.lua b/script/core/completion/keyword.lua index 89b90a73d..c390e0d11 100644 --- a/script/core/completion/keyword.lua +++ b/script/core/completion/keyword.lua @@ -242,27 +242,7 @@ end" if version ~= 'Lua 5.5' then return false end - if info.hasSpace then - results[#results+1] = { - label = 'global function', - kind = define.CompletionItemKind.Snippet, - insertTextFormat = 2, - insertText = "\z -function $1($2)\ -\t$0\ -end" - } - else - results[#results+1] = { - label = 'global function', - kind = define.CompletionItemKind.Snippet, - insertTextFormat = 2, - insertText = "\z -global function $1($2)\ -\t$0\ -end" - } - end + -- Note: No special completion for 'global function' syntax as it doesn't exist in Lua 5.5 return false end }, { 'nil' }, diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 95b594f8b..e0c81fef9 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -3141,44 +3141,12 @@ local function parseGlobal() -- Global declarations are only supported in Lua 5.5 if State.version ~= 'Lua 5.5' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = globalPos, - finish = getPosition(Tokens[Index] + 5, 'right'), - version = 'Lua 5.5', - info = { - version = State.version, - } - } + -- Return nil, true to indicate failed parse so it falls back to treating 'global' as identifier + return nil, true end Index = Index + 2 skipSpace() - local word = peekWord() - if not word then - missName() - return nil - end - - if word == 'function' then - local func = parseFunction(false, true) - local name = func.name - if name then - func.name = nil - name.type = GetToSetMap[name.type] - name.value = func - name.vstart = func.start - name.range = func.finish - name.globPos = globalPos - func.parent = name - pushActionIntoCurrentChunk(name) - return name - else - missName(func.keyword[2]) - pushActionIntoCurrentChunk(func) - return func - end - end local name = parseName(true) if not name then From 78942bcc377d79f330d8e5a0fb7f34f0a19aea8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:13:14 +0000 Subject: [PATCH 7/9] Add Lua 5.5 features to changelog Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) 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` From a55f5e769d2654d8d2348e99b80c6031a842de17 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:57:38 +0000 Subject: [PATCH 8/9] Fix global parsing to handle like goto - not a keyword when not in Lua 5.5 context Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- script/parser/compile.lua | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/script/parser/compile.lua b/script/parser/compile.lua index e0c81fef9..0cdb5ebc3 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, @@ -89,7 +90,6 @@ local KeyWord = { ['false'] = true, ['for'] = true, ['function'] = true, - ['global'] = true, ['if'] = true, ['in'] = true, ['local'] = true, @@ -209,7 +209,6 @@ local ChunkStartMap = { ['elseif'] = true, ['for'] = true, ['function'] = true, - ['global'] = true, ['if'] = true, ['local'] = true, ['repeat'] = true, @@ -1425,6 +1424,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 @@ -3139,12 +3150,6 @@ end local function parseGlobal() local globalPos = getPosition(Tokens[Index], 'left') - -- Global declarations are only supported in Lua 5.5 - if State.version ~= 'Lua 5.5' then - -- Return nil, true to indicate failed parse so it falls back to treating 'global' as identifier - return nil, true - end - Index = Index + 2 skipSpace() @@ -3946,7 +3951,7 @@ function parseAction() return parseLocal() end - if token == 'global' then + if token == 'global' and isKeyWord('global', Tokens[Index + 3]) then return parseGlobal() end From 686aa5fa3b4941388d1ac4c965402b1a05e9856c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 11:38:25 +0000 Subject: [PATCH 9/9] Move readonly-for-loop-vars check from diagnostic to parser stage Co-authored-by: CppCXY <40318218+CppCXY@users.noreply.github.com> --- locale/en-us/script.lua | 4 +- locale/es-419/script.lua | 2 + locale/ja-jp/script.lua | 2 + locale/pt-br/script.lua | 2 + locale/zh-cn/script.lua | 2 + locale/zh-tw/script.lua | 2 + .../diagnostics/readonly-for-loop-vars.lua | 69 ------------------- script/parser/compile.lua | 48 +++++++++++++ script/proto/diagnostic.lua | 8 --- test/diagnostics/readonly-for-loop-vars.lua | 11 +-- 10 files changed, 66 insertions(+), 84 deletions(-) delete mode 100644 script/core/diagnostics/readonly-for-loop-vars.lua diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 6f48fa2b4..ed3ede5a5 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -24,8 +24,6 @@ DIAG_UNUSED_VARARG = 'Unused vararg.' DIAG_REDEFINED_LOCAL = 'Redefined local `{}`.' -DIAG_READONLY_FOR_LOOP_VAR = -'Cannot assign to for-loop variable `{}` (read-only in Lua 5.5).' DIAG_DUPLICATE_INDEX = 'Duplicate index `{}`.' DIAG_DUPLICATE_METHOD = @@ -295,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 8d0450042..a350f43fa 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -295,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/script/core/diagnostics/readonly-for-loop-vars.lua b/script/core/diagnostics/readonly-for-loop-vars.lua deleted file mode 100644 index 465994599..000000000 --- a/script/core/diagnostics/readonly-for-loop-vars.lua +++ /dev/null @@ -1,69 +0,0 @@ -local files = require 'files' -local guide = require 'parser.guide' -local lang = require 'language' -local await = require 'await' -local config = require 'config' - ----@param source parser.object ----@return boolean -local function isForLoopVariable(source) - -- Check if this local is a for-loop variable - if source.type ~= 'local' then - return false - end - - local parent = source.parent - if not parent then - return false - end - - -- Check if parent is a numeric for-loop - if parent.type == 'loop' and parent.loc == source 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] == source then - return true - end - end - end - - return false -end - ----@async -return function (uri, callback) - local state = files.getState(uri) - if not state then - return - end - - -- Only check for Lua 5.5 - local version = config.get(uri, 'Lua.runtime.version') - if version ~= 'Lua 5.5' then - return - end - - ---@async - guide.eachSourceType(state.ast, 'setlocal', function (source) - await.delay() - - -- Get the original local declaration - local node = source.node - if not node then - return - end - - -- Check if this local is a for-loop variable - if isForLoopVariable(node) then - callback { - start = source.start, - finish = source.finish, - message = lang.script('DIAG_READONLY_FOR_LOOP_VAR', node[1]), - } - end - end) -end \ No newline at end of file diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 0cdb5ebc3..35422753a 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -263,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) @@ -2921,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 @@ -4011,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/proto/diagnostic.lua b/script/proto/diagnostic.lua index 1a07c8667..72761493e 100644 --- a/script/proto/diagnostic.lua +++ b/script/proto/diagnostic.lua @@ -174,14 +174,6 @@ m.register { status = 'Opened', } -m.register { - 'readonly-for-loop-vars', -} { - group = 'readonly', - severity = 'Error', - status = 'Opened', -} - m.register { 'undefined-global', 'global-in-nil-env', diff --git a/test/diagnostics/readonly-for-loop-vars.lua b/test/diagnostics/readonly-for-loop-vars.lua index e9101ea99..8cc89cf9f 100644 --- a/test/diagnostics/readonly-for-loop-vars.lua +++ b/test/diagnostics/readonly-for-loop-vars.lua @@ -1,17 +1,18 @@ -- 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 - = 5 -- Error: Cannot assign to for-loop variable + i = 5 -- This is now a parser error, so no diagnostic end ]] TEST [[ ---@language Lua 5.5 for k, v in pairs(t) do - = "new" -- Error: Cannot assign to for-loop variable - = 123 -- Error: Cannot assign to for-loop variable + k = "new" -- This is now a parser error, so no diagnostic + v = 123 -- This is now a parser error, so no diagnostic end ]] @@ -19,8 +20,8 @@ TEST [[ ---@language Lua 5.5 for i = 1, 10 do for j = 1, 5 do - = j -- Error: Cannot assign to outer for-loop variable - = i -- Error: Cannot assign to inner for-loop variable + i = j -- This is now a parser error, so no diagnostic + j = i -- This is now a parser error, so no diagnostic end end ]]