summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.md12
-rw-r--r--script/vm/compiler.lua16
-rw-r--r--script/vm/runner.lua168
-rw-r--r--test/tclient/tests/recursive-runner.lua81
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)