diff --git a/changelog.md b/changelog.md index 9fafc9649..58568425e 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +* `NEW` Setting: `Lua.hint.awaitPropagate`: When enabled, --@async propagates to the caller. ## 3.13.3 * `CHG` Update Love2d version diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua index da103ac18..3dfdaceb8 100644 --- a/locale/en-us/setting.lua +++ b/locale/en-us/setting.lua @@ -256,6 +256,9 @@ config.hint.arrayIndex.Disable = 'Disable hints of array index.' config.hint.await = 'If the called function is marked `---@async`, prompt `await` at the call.' +config.hint.awaitPropagate = +'Enable the propagation of `await`. When a function calls a function marked `---@async`,\z +it will be automatically marked as `---@async`.' config.hint.semicolon = 'If there is no semicolon at the end of the statement, display a virtual semicolon.' config.hint.semicolon.All = diff --git a/locale/ja-jp/setting.lua b/locale/ja-jp/setting.lua index fa5b8f233..afa05b641 100644 --- a/locale/ja-jp/setting.lua +++ b/locale/ja-jp/setting.lua @@ -256,6 +256,9 @@ config.hint.arrayIndex.Disable = -- TODO: need translate! 'Disable hints of array index.' config.hint.await = -- TODO: need translate! 'If the called function is marked `---@async`, prompt `await` at the call.' +config.hint.awaitPropagate = -- TODO: need translate! +'Enable the propagation of `await`. When a function calls a function marked `---@async`,\z +it will be automatically marked as `---@async`.' config.hint.semicolon = -- TODO: need translate! 'If there is no semicolon at the end of the statement, display a virtual semicolon.' config.hint.semicolon.All = -- TODO: need translate! diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua index b58fa7ceb..cce2e96c5 100644 --- a/locale/pt-br/setting.lua +++ b/locale/pt-br/setting.lua @@ -256,6 +256,9 @@ config.hint.arrayIndex.Disable = -- TODO: need translate! 'Disable hints of array index.' config.hint.await = -- TODO: need translate! 'If the called function is marked `---@async`, prompt `await` at the call.' +config.hint.awaitPropagate = -- TODO: need translate! +'Enable the propagation of `await`. When a function calls a function marked `---@async`,\z +it will be automatically marked as `---@async`.' config.hint.semicolon = -- TODO: need translate! 'If there is no semicolon at the end of the statement, display a virtual semicolon.' config.hint.semicolon.All = -- TODO: need translate! diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua index 8dcc4889c..a55f8d42e 100644 --- a/locale/zh-cn/setting.lua +++ b/locale/zh-cn/setting.lua @@ -255,6 +255,8 @@ config.hint.arrayIndex.Disable = '禁用数组索引提示。' config.hint.await = '如果调用的函数被标记为了 `---@async` ,则在调用处提示 `await` 。' +config.hint.awaitPropagate = +'启用 `await` 的传播, 当一个函数调用了一个`---@async`标记的函数时,会自动标记为`---@async`。' config.hint.semicolon = '若语句尾部没有分号,则显示虚拟分号。' config.hint.semicolon.All = diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua index 4747452b0..dfb716db8 100644 --- a/locale/zh-tw/setting.lua +++ b/locale/zh-tw/setting.lua @@ -255,6 +255,8 @@ config.hint.arrayIndex.Disable = '停用陣列索引提示。' config.hint.await = '如果呼叫的函數被標記為了 `---@async`,則在呼叫處提示 `await`。' +config.hint.awaitPropagate = +'啟用 `await` 的傳播,當一個函數呼叫了一個 `---@async` 標記的函數時,會自動標記為 `---@async`。' config.hint.semicolon = '若陳述式尾部沒有分號,則顯示虛擬分號。' config.hint.semicolon.All = diff --git a/script/config/template.lua b/script/config/template.lua index 3ff09ecc5..1f250bb24 100644 --- a/script/config/template.lua +++ b/script/config/template.lua @@ -369,6 +369,7 @@ local template = { 'Disable', }, ['Lua.hint.await'] = Type.Boolean >> true, + ['Lua.hint.awaitPropagate'] = Type.Boolean >> false, ['Lua.hint.arrayIndex'] = Type.String >> 'Auto' << { 'Enable', 'Auto', diff --git a/script/vm/doc.lua b/script/vm/doc.lua index c936d2f3f..257d6b022 100644 --- a/script/vm/doc.lua +++ b/script/vm/doc.lua @@ -1,8 +1,9 @@ -local files = require 'files' -local guide = require 'parser.guide' +local files = require 'files' +local await = require 'await' +local guide = require 'parser.guide' ---@class vm -local vm = require 'vm.vm' -local config = require 'config' +local vm = require 'vm.vm' +local config = require 'config' ---@class parser.object ---@field package _castTargetHead? parser.object | vm.global | false @@ -186,17 +187,37 @@ function vm.getDeprecated(value, deep) end ---@param value parser.object +---@param propagate boolean +---@param deepLevel integer? ---@return boolean -local function isAsync(value) +local function isAsync(value, propagate, deepLevel) if value.type == 'function' then - if not value.bindDocs then - return false - end - if value._async ~= nil then + if value._async ~= nil then --already calculated, directly return return value._async end - for _, doc in ipairs(value.bindDocs) do - if doc.type == 'doc.async' then + if value.bindDocs then --try parse the annotation + for _, doc in ipairs(value.bindDocs) do + if doc.type == 'doc.async' then + value._async = true + return true + end + end + end + if propagate then -- if enable async propagation, try check calling functions + if deepLevel and deepLevel > 50 then + return false + end + local isAsyncCall = vm.isAsyncCall + local callingAsync = guide.eachSourceType(value, 'call', function (source) + local nextLevel = (deepLevel or 1) + 1 + local ok = isAsyncCall(source, nextLevel) + if ok then --if any calling function is async, directly return + return ok + end + --if not, try check the next calling function + return nil + end) + if callingAsync then value._async = true return true end @@ -212,9 +233,12 @@ end ---@param value parser.object ---@param deep boolean? +---@param deepLevel integer? ---@return boolean -function vm.isAsync(value, deep) - if isAsync(value) then +function vm.isAsync(value, deep, deepLevel) + local uri = guide.getUri(value) + local propagate = config.get(uri, 'Lua.hint.awaitPropagate') + if isAsync(value, propagate, deepLevel) then return true end if deep then @@ -223,7 +247,7 @@ function vm.isAsync(value, deep) return false end for _, def in ipairs(defs) do - if isAsync(def) then + if isAsync(def, propagate, deepLevel) then return true end end @@ -325,16 +349,17 @@ function vm.isLinkedCall(node, index) end ---@param call parser.object +---@param deepLevel integer? ---@return boolean -function vm.isAsyncCall(call) - if vm.isAsync(call.node, true) then +function vm.isAsyncCall(call, deepLevel) + if vm.isAsync(call.node, true, deepLevel) then return true end if not call.args then return false end for i, arg in ipairs(call.args) do - if vm.isAsync(arg, true) + if vm.isAsync(arg, true, deepLevel) and isLinkedCall(call.node, i) then return true end @@ -485,3 +510,44 @@ function vm.docHasAttr(doc, key) end return false end + +---@async +local function clearAsyncPropagate(uri) + local propagate = config.get(uri, 'Lua.hint.awaitPropagate') + if not propagate then + return + end + local state = files.getState(uri) + if not state then + return + end + local marked = {} + local list = {} + guide.eachSourceType(state.ast, 'function', function (source) + marked[source] = true + list[#list+1] = source + end) + local pairs = pairs + local remove = table.remove + while #list > 0 do + local source = remove(list) + local refs = vm.getRefs(source) + for _, ref in pairs(refs) do + while ref and ref.type ~= 'function' do + ref = ref.parent + end + if ref and not marked[ref] then + ref._async = nil + marked[ref] = true + list[#list+1] = ref + end + end + await.delay() + end +end + +files.watch(function (ev, uri) ---@async + if ev == 'compile' then + clearAsyncPropagate(uri) + end +end) \ No newline at end of file