diff options
-rw-r--r-- | changelog.md | 12 | ||||
-rw-r--r-- | script/vm/compiler.lua | 16 | ||||
-rw-r--r-- | script/vm/runner.lua | 168 | ||||
-rw-r--r-- | test/tclient/tests/recursive-runner.lua | 81 |
4 files changed, 261 insertions, 16 deletions
diff --git a/changelog.md b/changelog.md index 7a20fc9e..1a82fac7 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,18 @@ ## 3.6.4 * `FIX` [#1698] [#1698]: https://github.com/sumneko/lua-language-server/issues/1698 +* `FIX` circulation reference in process analysis + ```lua + ---@type number + local x + + ---@type number + local y + + x = y + + y = x --> Can not infer `y` before + ``` ## 3.6.3 `2022-11-14` diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 6dbe41fe..ef0f7157 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -567,6 +567,7 @@ local function matchCall(source) local newNode = myNode:copy() newNode:removeNode(needRemove) newNode:setData('originNode', myNode) + newNode:setData('hasResolved', true) vm.setNode(source, newNode, true) end end @@ -640,7 +641,9 @@ local function bindAs(source) local doc = ases[index] if doc and doc.touch == source.finish then - vm.setNode(source, vm.compileNode(doc.as), true) + local asNode = vm.compileNode(doc.as) + asNode:setData 'hasResolved' + vm.setNode(source, asNode, true) return true end @@ -1186,6 +1189,7 @@ local compilerSwitch = util.switch() end) : case 'local' : case 'self' + ---@async ---@param source parser.object : call(function (source) compileLocal(source) @@ -1222,6 +1226,7 @@ local compilerSwitch = util.switch() return end vm.setNode(src, node, true) + node:setData('hasResolved', true) matchCall(src) end end) @@ -1244,11 +1249,13 @@ local compilerSwitch = util.switch() vm.compileNode(source.node) end) : case 'getlocal' + ---@async : call(function (source) if bindAs(source) then return end vm.compileNode(source.node) + vm.waitResolveRunner(source) end) : case 'setfield' : case 'setmethod' @@ -1929,6 +1936,13 @@ function vm.compileNode(source) end end + if source.type == 'getlocal' then + ---@cast source parser.object + vm.storeWaitingRunner(source) + ---@diagnostic disable-next-line: await-in-sync + vm.waitResolveRunner(source) + end + local cache = vm.getNode(source) if cache ~= nil then return cache diff --git a/script/vm/runner.lua b/script/vm/runner.lua index 4ec637e8..341126c6 100644 --- a/script/vm/runner.lua +++ b/script/vm/runner.lua @@ -1,6 +1,7 @@ ---@class vm local vm = require 'vm.vm' local guide = require 'parser.guide' +local linked = require 'linked-table' ---@alias vm.runner.callback fun(src: parser.object, node?: vm.node) @@ -344,24 +345,165 @@ function mt:lookIntoBlock(block, topNode) return topNode end +---@alias runner.info { source?: parser.object, loc: parser.object } + +---@type thread? +local masterRunner = nil +---@type table<thread, runner.info> +local runnerInfo = setmetatable({}, { + __mode = 'k', + __index = function (self, k) + self[k] = {} + return self[k] + end +}) +---@type linked-table? +local runnerList = nil + +---@async +---@param info runner.info +local function waitResolve(info) + while true do + if not info.source then + break + end + if info.source.node == info.loc then + break + end + local node = vm.getNode(info.source) + if node and node:getData('hasResolved') then + break + end + coroutine.yield() + end + info.source = nil +end + +---@async ---@param loc parser.object ---@param callback vm.runner.callback function vm.launchRunner(loc, callback) - local main = guide.getParentBlock(loc) - if not main then + local locNode = vm.getNode(loc) + if not locNode then + return + end + + local function resumeMaster() + for i = 1, 10010 do + if not runnerList or runnerList:getSize() == 0 then + return + end + local allWaiting = true + for runner in runnerList:pairs() do + local info = runnerInfo[runner] + local waitingSource = info.source + if coroutine.status(runner) == 'suspended' then + local suc, err = coroutine.resume(runner) + if not suc then + log.error(debug.traceback(runner, err)) + end + else + runnerList:pop(runner) + end + if not waitingSource or waitingSource ~= info.source then + allWaiting = false + end + end + if runnerList:getSize() == 0 then + return + end + if allWaiting or i == 10000 then + local lines = {} + lines[#lines+1] = 'Dead lock:' + lines[#lines+1] = guide.getUri(loc) + for runner in runnerList:pairs() do + local info = runnerInfo[runner] + lines[#lines+1] = string.format('Runner `%s` at %d waiting for `%s` at %d' + , loc[1] + , loc.start + , info.source and info.source[1] or '' + , info.source and info.source.start or 0 + ) + end + local msg = table.concat(lines, '\n') + log.error(msg) + end + end + end + + local function launch() + local main = guide.getParentBlock(loc) + if not main then + return + end + local self = setmetatable({ + _loc = loc, + _casts = {}, + _mark = {}, + _has = {}, + _main = main, + _uri = guide.getUri(loc), + _callback = callback, + }, mt) + + self:collect() + + self:lookIntoBlock(main, locNode:copy()) + + locNode:setData('runner', nil) + end + + local co = coroutine.create(launch) + locNode:setData('runner', co) + local info = runnerInfo[co] + info.loc = loc + + if not runnerList then + runnerList = linked() + end + runnerList:pushTail(co) + + if not masterRunner then + masterRunner = coroutine.running() + resumeMaster() + masterRunner = nil return end - local self = setmetatable({ - _loc = loc, - _casts = {}, - _mark = {}, - _has = {}, - _main = main, - _uri = guide.getUri(loc), - _callback = callback, - }, mt) +end - self:collect() +---@async +---@param source parser.object +function vm.waitResolveRunner(source) + local running = coroutine.running() + if not masterRunner or running == masterRunner then + return + end + + local loc = source.node + local locNode = vm.getNode(loc) + if not locNode then + return + end + local runner = locNode:getData('runner') + if not runner or runner == running then + return + end + + local info = runnerInfo[running] + if info.loc == loc then + return + end + waitResolve(info) +end + +---@param source parser.object +function vm.storeWaitingRunner(source) + local sourceNode = vm.getNode(source) + if sourceNode and sourceNode:getData 'hasResolved' then + return + end - self:lookIntoBlock(main, vm.getNode(loc):copy()) + local running = coroutine.running() + local info = runnerInfo[running] + info.source = source end diff --git a/test/tclient/tests/recursive-runner.lua b/test/tclient/tests/recursive-runner.lua index 923314c3..3e3b5bba 100644 --- a/test/tclient/tests/recursive-runner.lua +++ b/test/tclient/tests/recursive-runner.lua @@ -1,12 +1,17 @@ local lclient = require 'lclient' local ws = require 'workspace' local await = require 'await' +local config = require 'config' ---@async lclient():start(function (client) client:registerFakers() client:initialize() + config.set(nil, 'Lua.diagnostics.enable', false) + + ws.awaitReady() + client:notify('textDocument/didOpen', { textDocument = { uri = 'file://test.lua', @@ -26,8 +31,6 @@ y = x } }) - ws.awaitReady() - await.sleep(0.1) local hover1 = client:awaitRequest('textDocument/hover', { @@ -42,4 +45,78 @@ y = x assert(hover1.contents.value:find 'number') assert(hover2.contents.value:find 'number') + + client:notify('textDocument/didOpen', { + textDocument = { + uri = 'file://test.lua', + languageId = 'lua', + version = 1, + text = [[ +---@type number +local x + +---@type number +local y + +---@type number +local z + +x = y +y = z +z = y +x = y +x = z +z = x +y = x +]] + } + }) + + + await.sleep(0.1) + + local hover1 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file://test.lua' }, + position = { line = 9, character = 0 }, + }) + + local hover2 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file://test.lua' }, + position = { line = 10, character = 0 }, + }) + + local hover3 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file://test.lua' }, + position = { line = 11, character = 0 }, + }) + + local hover4 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file://test.lua' }, + position = { line = 12, character = 0 }, + }) + + local hover5 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file://test.lua' }, + position = { line = 13, character = 0 }, + }) + + local hover6 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file://test.lua' }, + position = { line = 14, character = 0 }, + }) + + local hover7 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file://test.lua' }, + position = { line = 15, character = 0 }, + }) + + assert(hover1.contents.value:find 'number') + assert(hover2.contents.value:find 'number') + assert(hover3.contents.value:find 'number') + assert(hover4.contents.value:find 'number') + assert(hover5.contents.value:find 'number') + assert(hover6.contents.value:find 'number') + assert(hover7.contents.value:find 'number') + + config.set(nil, 'Lua.diagnostics.enable', true) end) |