diff options
-rw-r--r-- | script/core/guide.lua | 4744 | ||||
-rw-r--r-- | script/core/guide2.lua | 4837 | ||||
-rw-r--r-- | script/core/linker.lua | 100 | ||||
-rw-r--r-- | script/parser/guide.lua | 9 | ||||
-rw-r--r-- | test/basic/linker.lua | 16 | ||||
-rw-r--r-- | test/basic/linker.txt | 16 |
6 files changed, 5048 insertions, 4674 deletions
diff --git a/script/core/guide.lua b/script/core/guide.lua index 64192297..e0123987 100644 --- a/script/core/guide.lua +++ b/script/core/guide.lua @@ -1,2754 +1,27 @@ -local util = require 'utility' -local error = error -local type = type -local next = next -local tostring = tostring -local print = print -local ipairs = ipairs -local tableInsert = table.insert -local tableUnpack = table.unpack -local tableRemove = table.remove -local tableMove = table.move -local tableSort = table.sort -local tableConcat = table.concat -local mathType = math.type -local pairs = pairs -local setmetatable = setmetatable -local assert = assert -local select = select -local osClock = os.clock -local tonumber = tonumber -local tointeger = math.tointeger -local DEVELOP = _G.DEVELOP -local log = log -local _G = _G +local linker = require 'core.linker' +local guide = require 'parser.guide' -local function logWarn(...) - log.warn(...) -end +local osClock = os.clock +local pairs = pairs local m = {} -m.ANY = {"<ANY>"} - -local blockTypes = { - ['while'] = true, - ['in'] = true, - ['loop'] = true, - ['repeat'] = true, - ['do'] = true, - ['function'] = true, - ['ifblock'] = true, - ['elseblock'] = true, - ['elseifblock'] = true, - ['main'] = true, -} - -local breakBlockTypes = { - ['while'] = true, - ['in'] = true, - ['loop'] = true, - ['repeat'] = true, -} - -m.childMap = { - ['main'] = {'#', 'docs'}, - ['repeat'] = {'#', 'filter'}, - ['while'] = {'filter', '#'}, - ['in'] = {'keys', '#'}, - ['loop'] = {'loc', 'max', 'step', '#'}, - ['if'] = {'#'}, - ['ifblock'] = {'filter', '#'}, - ['elseifblock'] = {'filter', '#'}, - ['elseblock'] = {'#'}, - ['setfield'] = {'node', 'field', 'value'}, - ['setglobal'] = {'value'}, - ['local'] = {'attrs', 'value'}, - ['setlocal'] = {'value'}, - ['return'] = {'#'}, - ['do'] = {'#'}, - ['select'] = {'vararg'}, - ['table'] = {'#'}, - ['tableindex'] = {'index', 'value'}, - ['tablefield'] = {'field', 'value'}, - ['function'] = {'args', '#'}, - ['funcargs'] = {'#'}, - ['setmethod'] = {'node', 'method', 'value'}, - ['getmethod'] = {'node', 'method'}, - ['setindex'] = {'node', 'index', 'value'}, - ['getindex'] = {'node', 'index'}, - ['paren'] = {'exp'}, - ['call'] = {'node', 'args'}, - ['callargs'] = {'#'}, - ['getfield'] = {'node', 'field'}, - ['list'] = {'#'}, - ['binary'] = {1, 2}, - ['unary'] = {1}, - - ['doc'] = {'#'}, - ['doc.class'] = {'class', '#extends', 'comment'}, - ['doc.type'] = {'#types', '#enums', 'name', 'comment'}, - ['doc.alias'] = {'alias', 'extends', 'comment'}, - ['doc.param'] = {'param', 'extends', 'comment'}, - ['doc.return'] = {'#returns', 'comment'}, - ['doc.field'] = {'field', 'extends', 'comment'}, - ['doc.generic'] = {'#generics', 'comment'}, - ['doc.generic.object'] = {'generic', 'extends', 'comment'}, - ['doc.vararg'] = {'vararg', 'comment'}, - ['doc.type.array'] = {'node'}, - ['doc.type.table'] = {'node', 'key', 'value', 'comment'}, - ['doc.type.function'] = {'#args', '#returns', 'comment'}, - ['doc.type.typeliteral'] = {'node'}, - ['doc.type.arg'] = {'extends'}, - ['doc.overload'] = {'overload', 'comment'}, - ['doc.see'] = {'name', 'field', 'comment'}, - ['doc.diagnostic'] = {'#names', 'comment'}, -} - -m.actionMap = { - ['main'] = {'#'}, - ['repeat'] = {'#'}, - ['while'] = {'#'}, - ['in'] = {'#'}, - ['loop'] = {'#'}, - ['if'] = {'#'}, - ['ifblock'] = {'#'}, - ['elseifblock'] = {'#'}, - ['elseblock'] = {'#'}, - ['do'] = {'#'}, - ['function'] = {'#'}, - ['funcargs'] = {'#'}, -} - -local TypeSort = { - ['boolean'] = 1, - ['string'] = 2, - ['integer'] = 3, - ['number'] = 4, - ['table'] = 5, - ['function'] = 6, - ['true'] = 101, - ['false'] = 102, - ['nil'] = 999, -} - -local NIL = setmetatable({'<nil>'}, { __tostring = function () return 'nil' end }) - ---- 是否是字面量 ----@param obj parser.guide.object ----@return boolean -function m.isLiteral(obj) - local tp = obj.type - return tp == 'nil' - or tp == 'boolean' - or tp == 'string' - or tp == 'number' - or tp == 'table' - or tp == 'function' -end - ---- 获取字面量 ----@param obj parser.guide.object ----@return any -function m.getLiteral(obj) - local tp = obj.type - if tp == 'boolean' then - return obj[1] - elseif tp == 'string' then - return obj[1] - elseif tp == 'number' then - return obj[1] - end - return nil -end - ---- 寻找父函数 ----@param obj parser.guide.object ----@return parser.guide.object -function m.getParentFunction(obj) - for _ = 1, 1000 do - obj = obj.parent - if not obj then - break - end - local tp = obj.type - if tp == 'function' or tp == 'main' then - return obj - end - end - return nil -end - ---- 寻找父的table类型 doc.type.table ----@param obj parser.guide.object ----@return parser.guide.object -function m.getParentDocTypeTable(obj) - for _ = 1, 1000 do - local parent = obj.parent - if not parent then - return nil - end - if parent.type == 'doc.type.table' then - return obj - end - obj = parent - end - error('guide.getParentDocTypeTable overstack') -end - ---- 寻找所在区块 ----@param obj parser.guide.object ----@return parser.guide.object -function m.getBlock(obj) - for _ = 1, 1000 do - if not obj then - return nil - end - local tp = obj.type - if blockTypes[tp] then - return obj - end - if obj == obj.parent then - error('obj == obj.parent?', obj.type) - end - obj = obj.parent - end - error('guide.getBlock overstack') -end - ---- 寻找所在父区块 ----@param obj parser.guide.object ----@return parser.guide.object -function m.getParentBlock(obj) - for _ = 1, 1000 do - obj = obj.parent - if not obj then - return nil - end - local tp = obj.type - if blockTypes[tp] then - return obj - end - end - error('guide.getParentBlock overstack') -end - ---- 寻找所在可break的父区块 ----@param obj parser.guide.object ----@return parser.guide.object -function m.getBreakBlock(obj) - for _ = 1, 1000 do - obj = obj.parent - if not obj then - return nil - end - local tp = obj.type - if breakBlockTypes[tp] then - return obj - end - if tp == 'function' then - return nil - end - end - error('guide.getBreakBlock overstack') -end - ---- 寻找doc的主体 ----@param obj parser.guide.object ----@return parser.guide.object -function m.getDocState(obj) - for _ = 1, 1000 do - local parent = obj.parent - if not parent then - return obj - end - if parent.type == 'doc' then - return obj - end - obj = parent - end - error('guide.getDocState overstack') -end - ---- 寻找所在父类型 ----@param obj parser.guide.object ----@return parser.guide.object -function m.getParentType(obj, want) - for _ = 1, 1000 do - obj = obj.parent - if not obj then - return nil - end - if want == obj.type then - return obj - end - end - error('guide.getParentType overstack') -end - ---- 寻找根区块 ----@param obj parser.guide.object ----@return parser.guide.object -function m.getRoot(obj) - local source = obj - if source._root then - return source._root - end - for _ = 1, 1000 do - if obj.type == 'main' then - source._root = obj - return obj - end - local parent = obj.parent - if not parent then - return nil - end - obj = parent - end - error('guide.getRoot overstack') -end - ----@param obj parser.guide.object ----@return string -function m.getUri(obj) - if obj.uri then - return obj.uri - end - local root = m.getRoot(obj) - if root then - return root.uri - end - return '' -end - -function m.getENV(source, start) - if not start then - start = 1 - end - return m.getLocal(source, '_ENV', start) - or m.getLocal(source, '@fenv', start) -end - ---- 寻找函数的不定参数,返回不定参在第几个参数上,以及该参数对象。 ---- 如果函数是主函数,则返回`0, nil`。 ----@return table ----@return integer -function m.getFunctionVarArgs(func) - if func.type == 'main' then - return 0, nil - end - if func.type ~= 'function' then - return nil, nil - end - local args = func.args - if not args then - return nil, nil - end - for i = 1, #args do - local arg = args[i] - if arg.type == '...' then - return i, arg - end - end - return nil, nil -end - ---- 获取指定区块中可见的局部变量 ----@param block table ----@param name string {comment = '变量名'} ----@param pos integer {comment = '可见位置'} -function m.getLocal(block, name, pos) - block = m.getBlock(block) - for _ = 1, 1000 do - if not block then - return nil - end - local locals = block.locals - local res - if not locals then - goto CONTINUE - end - for i = 1, #locals do - local loc = locals[i] - if loc.effect > pos then - break - end - if loc[1] == name then - if not res or res.effect < loc.effect then - res = loc - end - end - end - if res then - return res, res - end - ::CONTINUE:: - block = m.getParentBlock(block) - end - error('guide.getLocal overstack') -end - ---- 获取指定区块中所有的可见局部变量名称 -function m.getVisibleLocals(block, pos) - local result = {} - m.eachSourceContain(m.getRoot(block), pos, function (source) - local locals = source.locals - if locals then - for i = 1, #locals do - local loc = locals[i] - local name = loc[1] - if loc.effect <= pos then - result[name] = loc - end - end - end - end) - return result -end - ---- 获取指定区块中可见的标签 ----@param block table ----@param name string {comment = '标签名'} -function m.getLabel(block, name) - block = m.getBlock(block) - for _ = 1, 1000 do - if not block then - return nil - end - local labels = block.labels - if labels then - local label = labels[name] - if label then - return label - end - end - if block.type == 'function' then - return nil - end - block = m.getParentBlock(block) - end - error('guide.getLocal overstack') -end - -function m.getStartFinish(source) - local start = source.start - local finish = source.finish - if not start then - local first = source[1] - if not first then - return nil, nil - end - local last = source[#source] - start = first.start - finish = last.finish - end - return start, finish -end - -function m.getRange(source) - local start = source.vstart or source.start - local finish = source.range or source.finish - if not start then - local first = source[1] - if not first then - return nil, nil - end - local last = source[#source] - start = first.vstart or first.start - finish = last.range or last.finish - end - return start, finish -end - ---- 判断source是否包含offset -function m.isContain(source, offset) - local start, finish = m.getStartFinish(source) - if not start then - return false - end - return start <= offset and finish >= offset -end - ---- 判断offset在source的影响范围内 ---- ---- 主要针对赋值等语句时,key包含value -function m.isInRange(source, offset) - local start, finish = m.getRange(source) - if not start then - return false - end - return start <= offset and finish >= offset -end - -function m.isBetween(source, tStart, tFinish) - local start, finish = m.getStartFinish(source) - if not start then - return false - end - return start <= tFinish and finish >= tStart -end - -function m.isBetweenRange(source, tStart, tFinish) - local start, finish = m.getRange(source) - if not start then - return false - end - return start <= tFinish and finish >= tStart -end - ---- 添加child -function m.addChilds(list, obj, map) - local keys = map[obj.type] - if keys then - for i = 1, #keys do - local key = keys[i] - if key == '#' then - for i = 1, #obj do - list[#list+1] = obj[i] - end - elseif obj[key] then - list[#list+1] = obj[key] - elseif type(key) == 'string' - and key:sub(1, 1) == '#' then - key = key:sub(2) - if obj[key] then - for i = 1, #obj[key] do - list[#list+1] = obj[key][i] - end - end - end - end - end -end - ---- 遍历所有包含offset的source -function m.eachSourceContain(ast, offset, callback) - local list = { ast } - local mark = {} - while true do - local len = #list - if len == 0 then - return - end - local obj = list[len] - list[len] = nil - if not mark[obj] then - mark[obj] = true - if m.isInRange(obj, offset) then - if m.isContain(obj, offset) then - local res = callback(obj) - if res ~= nil then - return res - end - end - m.addChilds(list, obj, m.childMap) - end - end - end -end - ---- 遍历所有在某个范围内的source -function m.eachSourceBetween(ast, start, finish, callback) - local list = { ast } - local mark = {} - while true do - local len = #list - if len == 0 then - return - end - local obj = list[len] - list[len] = nil - if not mark[obj] then - mark[obj] = true - if m.isBetweenRange(obj, start, finish) then - if m.isBetween(obj, start, finish) then - local res = callback(obj) - if res ~= nil then - return res - end - end - m.addChilds(list, obj, m.childMap) - end - end - end -end - ---- 遍历所有指定类型的source -function m.eachSourceType(ast, type, callback) - local cache = ast._typeCache - if not cache then - cache = {} - ast._typeCache = cache - m.eachSource(ast, function (source) - local tp = source.type - if not tp then - return - end - local myCache = cache[tp] - if not myCache then - myCache = {} - cache[tp] = myCache - end - myCache[#myCache+1] = source - end) - end - local myCache = cache[type] - if not myCache then - return - end - for i = 1, #myCache do - callback(myCache[i]) - end -end - ---- 遍历所有的source -function m.eachSource(ast, callback) - local list = { ast } - local mark = {} - local index = 1 - while true do - local obj = list[index] - if not obj then - return - end - list[index] = false - index = index + 1 - if not mark[obj] then - mark[obj] = true - callback(obj) - m.addChilds(list, obj, m.childMap) - end - end -end - ---- 获取指定的 special -function m.eachSpecialOf(ast, name, callback) - local root = m.getRoot(ast) - if not root.specials then - return - end - local specials = root.specials[name] - if not specials then - return - end - for i = 1, #specials do - callback(specials[i]) - end -end - ---- 获取偏移对应的坐标 ----@param lines table ----@return integer row ----@return integer col -function m.positionOf(lines, offset) - if offset < 1 then - return 0, 0 - end - local lastLine = lines[#lines] - if offset > lastLine.finish then - return #lines, offset - lastLine.start - end - local min = 1 - local max = #lines - for _ = 1, 100 do - if max <= min then - local line = lines[min] - return min, offset - line.start + 1 - end - local row = (max - min) // 2 + min - local line = lines[row] - if offset < line.start then - max = row - 1 - elseif offset > line.finish then - min = row + 1 - else - return row, offset - line.start + 1 - end - end - error('Stack overflow!') -end - ---- 获取坐标对应的偏移 ----@param lines table ----@param row integer ----@param col integer ----@return integer {name = 'offset'} -function m.offsetOf(lines, row, col) - if row < 1 then - return 0 - end - if row > #lines then - local lastLine = lines[#lines] - return lastLine.finish - end - local line = lines[row] - local len = line.finish - line.start + 1 - if col < 0 then - return line.start - elseif col > len then - return line.finish - else - return line.start + col - 1 - end -end - -function m.lineContent(lines, text, row, ignoreNL) - local line = lines[row] - if not line then - return '' - end - if ignoreNL then - return text:sub(line.start, line.range) - else - return text:sub(line.start, line.finish) - end -end - -function m.lineRange(lines, row, ignoreNL) - local line = lines[row] - if not line then - return 0, 0 - end - if ignoreNL then - return line.start, line.range - else - return line.start, line.finish - end -end - -function m.lineData(lines, row) - return lines[row] -end - -function m.getKeyTypeOfLiteral(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'field' - or tp == 'method' then - return 'string' - elseif tp == 'string' then - return 'string' - elseif tp == 'number' then - return 'number' - elseif tp == 'boolean' then - return 'boolean' - end -end - -function m.getKeyType(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'getglobal' - or tp == 'setglobal' then - return 'string' - elseif tp == 'local' - or tp == 'getlocal' - or tp == 'setlocal' then - return 'local' - elseif tp == 'getfield' - or tp == 'setfield' - or tp == 'tablefield' then - return 'string' - elseif tp == 'getmethod' - or tp == 'setmethod' then - return 'string' - elseif tp == 'getindex' - or tp == 'setindex' - or tp == 'tableindex' then - return m.getKeyTypeOfLiteral(obj.index) - elseif tp == 'field' - or tp == 'method' - or tp == 'doc.see.field' then - return 'string' - elseif tp == 'doc.class' then - return 'string' - elseif tp == 'doc.alias' then - return 'string' - elseif tp == 'doc.field' then - return 'string' - elseif tp == 'dummy' then - return 'string' - end - return m.getKeyTypeOfLiteral(obj) -end - -function m.getKeyNameOfLiteral(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'field' - or tp == 'method' then - return obj[1] - elseif tp == 'string' then - local s = obj[1] - if s then - return s - end - elseif tp == 'number' then - local n = obj[1] - if n then - return ('%s'):format(util.viewLiteral(obj[1])) - end - elseif tp == 'boolean' then - local b = obj[1] - if b then - return tostring(b) - end - end -end - -function m.getKeyName(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'getglobal' - or tp == 'setglobal' then - return obj[1] - elseif tp == 'local' - or tp == 'getlocal' - or tp == 'setlocal' then - return obj[1] - elseif tp == 'getfield' - or tp == 'setfield' - or tp == 'tablefield' then - if obj.field then - return obj.field[1] - end - elseif tp == 'getmethod' - or tp == 'setmethod' then - if obj.method then - return obj.method[1] - end - elseif tp == 'getindex' - or tp == 'setindex' - or tp == 'tableindex' then - return m.getKeyNameOfLiteral(obj.index) - elseif tp == 'field' - or tp == 'method' - or tp == 'doc.see.field' then - return obj[1] - elseif tp == 'doc.class' then - return obj.class[1] - elseif tp == 'doc.alias' then - return obj.alias[1] - elseif tp == 'doc.field' then - return obj.field[1] - elseif tp == 'dummy' then - return obj[1] - end - return m.getKeyNameOfLiteral(obj) -end - -function m.getSimpleName(obj) - if obj.type == 'call' then - local node = obj.node - if not node then - return - end - if node.special == 'rawset' - or node.special == 'rawget' then - local key = obj.args and obj.args[2] - return m.getKeyName(key) - end - return ('%p'):format(obj) - elseif obj.type == 'table' then - return ('%p'):format(obj) - elseif obj.type == 'select' then - return ('%p'):format(obj) - elseif obj.type == 'string' then - return ('%p'):format(obj) - elseif obj.type == 'doc.class.name' - or obj.type == 'doc.type.name' - or obj.type == 'doc.see.name' then - return ('%s'):format(obj[1]) - elseif obj.type == 'doc.class' then - return ('%s'):format(obj.class[1]) - end - return m.getKeyName(obj) -end - ---- 测试 a 到 b 的路径(不经过函数,不考虑 goto), ---- 每个路径是一个 block 。 ---- ---- 如果 a 在 b 的前面,返回 `"before"` 加上 2个`list<block>` ---- ---- 如果 a 在 b 的后面,返回 `"after"` 加上 2个`list<block>` ---- ---- 否则返回 `false` ---- ---- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。 ----@param a table ----@param b table ----@return string|boolean mode ----@return table pathA? ----@return table pathB? -function m.getPath(a, b, sameFunction) - --- 首先测试双方在同一个函数内 - if sameFunction and m.getParentFunction(a) ~= m.getParentFunction(b) then - return false - end - local mode - local objA - local objB - if a.finish < b.start then - mode = 'before' - objA = a - objB = b - elseif a.start > b.finish then - mode = 'after' - objA = b - objB = a - else - return 'equal', {}, {} - end - local pathA = {} - local pathB = {} - for _ = 1, 1000 do - objA = m.getParentBlock(objA) - pathA[#pathA+1] = objA - if (not sameFunction and objA.type == 'function') or objA.type == 'main' then - break - end - end - for _ = 1, 1000 do - objB = m.getParentBlock(objB) - pathB[#pathB+1] = objB - if (not sameFunction and objA.type == 'function') or objB.type == 'main' then - break - end - end - -- pathA: {1, 2, 3, 4, 5} - -- pathB: {5, 6, 2, 3} - local top = #pathB - local start - for i = #pathA, 1, -1 do - local currentBlock = pathA[i] - if currentBlock == pathB[top] then - start = i - break - end - end - if not start then - return nil - end - -- pathA: { 1, 2, 3} - -- pathB: {5, 6, 2, 3} - local extra = 0 - local align = top - start - for i = start, 1, -1 do - local currentA = pathA[i] - local currentB = pathB[i+align] - if currentA ~= currentB then - extra = i - break - end - end - -- pathA: {1} - local resultA = {} - for i = extra, 1, -1 do - resultA[#resultA+1] = pathA[i] - end - -- pathB: {5, 6} - local resultB = {} - for i = extra + align, 1, -1 do - resultB[#resultB+1] = pathB[i] - end - return mode, resultA, resultB -end - --- 根据语法,单步搜索定义 -local function stepRefOfLocal(loc, mode) - local results = {} - if loc.start ~= 0 then - results[#results+1] = loc - end - local refs = loc.ref - if not refs then - return results - end - for i = 1, #refs do - local ref = refs[i] - if ref.start == 0 then - goto CONTINUE - end - if mode == 'def' then - if ref.type == 'local' - or ref.type == 'setlocal' then - results[#results+1] = ref - end - else - if ref.type == 'local' - or ref.type == 'setlocal' - or ref.type == 'getlocal' then - results[#results+1] = ref - end - end - ::CONTINUE:: - end - return results -end - -local function stepRefOfLabel(label, mode) - local results = { label } - if not label or mode == 'def' then - return results - end - local refs = label.ref - if not refs then - return results - end - for i = 1, #refs do - local ref = refs[i] - results[#results+1] = ref - end - return results -end - -local function stepRefOfDocType(status, obj, mode) - local results = {} - if obj.type == 'doc.class.name' - or obj.type == 'doc.type.name' - or obj.type == 'doc.alias.name' - or obj.type == 'doc.extends.name' - or obj.type == 'doc.see.name' then - local name = obj[1] - if not name or not status.interface.docType then - return results - end - if name == 'nil' - or name == 'any' - or name == 'boolean' - or name == 'string' - or name == 'table' - or name == 'number' - or name == 'integer' - or name == 'function' - or name == 'table' - or name == 'thread' - or name == 'userdata' - or name == 'lightuserdata' then - mode = 'def' - end - local docs = status.interface.docType(name) - for i = 1, #docs do - local doc = docs[i] - if mode == 'def' then - if doc.type == 'doc.class.name' - or doc.type == 'doc.alias.name' then - results[#results+1] = doc - end - else - results[#results+1] = doc - end - end - else - results[#results+1] = obj - end - return results -end - -function m.getStepRef(status, obj, mode) - if obj.type == 'getlocal' - or obj.type == 'setlocal' then - return stepRefOfLocal(obj.node, mode) - end - if obj.type == 'local' then - return stepRefOfLocal(obj, mode) - end - if obj.type == 'label' then - return stepRefOfLabel(obj, mode) - end - if obj.type == 'goto' then - return stepRefOfLabel(obj.node, mode) - end - if obj.type == 'doc.class.name' - or obj.type == 'doc.type.name' - or obj.type == 'doc.extends.name' - or obj.type == 'doc.alias.name' then - return stepRefOfDocType(status, obj, mode) - end - if obj.type == 'function' then - return { obj } - end - return nil -end - --- 根据语法,单步搜索field -local function stepFieldOfLocal(loc) - local results = {} - local refs = loc.ref - for i = 1, #refs do - local ref = refs[i] - if ref.type == 'setglobal' - or ref.type == 'getglobal' then - results[#results+1] = ref - elseif ref.type == 'getlocal' then - local nxt = ref.next - if nxt then - if nxt.type == 'setfield' - or nxt.type == 'getfield' - or nxt.type == 'setmethod' - or nxt.type == 'getmethod' - or nxt.type == 'setindex' - or nxt.type == 'getindex' then - results[#results+1] = nxt - end - end - end - end - return results -end -local function stepFieldOfTable(tbl) - local result = {} - for i = 1, #tbl do - result[i] = tbl[i] - end - return result -end -function m.getStepField(obj) - if obj.type == 'getlocal' - or obj.type == 'setlocal' then - return stepFieldOfLocal(obj.node) - end - if obj.type == 'local' then - return stepFieldOfLocal(obj) - end - if obj.type == 'table' then - return stepFieldOfTable(obj) - end -end - -local function convertSimpleList(list) - local simple = {} - for i = #list, 1, -1 do - local c = list[i] - if c.type == 'getglobal' - or c.type == 'setglobal' then - if c.special == '_G' then - simple.mode = 'global' - goto CONTINUE - end - local loc = c.node - if loc.special == '_G' then - simple.mode = 'global' - if not simple.node then - simple.node = c - end - else - simple.mode = 'local' - simple[#simple+1] = m.getSimpleName(loc) - if not simple.node then - simple.node = loc - end - end - elseif c.type == 'getlocal' - or c.type == 'setlocal' then - if c.special == '_G' then - simple.mode = 'global' - goto CONTINUE - end - simple.mode = 'local' - if not simple.node then - simple.node = c.node - end - elseif c.type == 'local' then - simple.mode = 'local' - if not simple.node then - simple.node = c - end - else - if not simple.node then - simple.node = c - end - end - simple[#simple+1] = m.getSimpleName(c) or m.ANY - ::CONTINUE:: - end - if simple.mode == 'global' and #simple == 0 then - simple[1] = '_G' - simple.node = list[#list] - end - return simple -end - --- 搜索 `a.b.c` 的等价表达式 -local function buildSimpleList(obj, max) - local list = {} - local cur = obj - local limit = max and (max + 1) or 11 - for i = 1, max or limit do - if i == limit then - return nil - end - while cur.type == 'paren' do - cur = cur.exp - if not cur then - return nil - end - end - if cur.type == 'setfield' - or cur.type == 'getfield' - or cur.type == 'setmethod' - or cur.type == 'getmethod' - or cur.type == 'setindex' - or cur.type == 'getindex' then - list[i] = cur - cur = cur.node - elseif cur.type == 'tablefield' - or cur.type == 'tableindex' then - list[i] = cur - cur = cur.parent.parent - if cur.type == 'return' then - list[i+1] = list[i].parent - break - end - elseif cur.type == 'getlocal' - or cur.type == 'setlocal' - or cur.type == 'local' then - list[i] = cur - break - elseif cur.type == 'setglobal' - or cur.type == 'getglobal' then - list[i] = cur - break - elseif cur.type == 'select' - or cur.type == 'table' - or cur.type == 'call' then - list[i] = cur - break - elseif cur.type == 'string' then - list[i] = cur - break - elseif cur.type == '...' then - list[i] = cur - break - elseif cur.type == 'doc.class.name' - or cur.type == 'doc.type.name' - or cur.type == 'doc.class' - or cur.type == 'doc.see.name' then - list[i] = cur - break - elseif cur.type == 'doc.see.field' then - list[i] = cur - cur = cur.parent.name - elseif cur.type == 'function' - or cur.type == 'main' then - break - else - return nil - end - end - return convertSimpleList(list) -end - -function m.getSimple(obj, max) - local simpleList - if obj.type == 'getfield' - or obj.type == 'setfield' - or obj.type == 'getmethod' - or obj.type == 'setmethod' - or obj.type == 'getindex' - or obj.type == 'setindex' - or obj.type == 'local' - or obj.type == 'getlocal' - or obj.type == 'setlocal' - or obj.type == 'setglobal' - or obj.type == 'getglobal' - or obj.type == 'tablefield' - or obj.type == 'tableindex' - or obj.type == 'select' - or obj.type == 'call' - or obj.type == 'table' - or obj.type == 'string' - or obj.type == '...' - or obj.type == 'doc.class.name' - or obj.type == 'doc.class' - or obj.type == 'doc.type.name' - or obj.type == 'doc.see.name' - or obj.type == 'doc.see.field' then - simpleList = buildSimpleList(obj, max) - elseif obj.type == 'field' - or obj.type == 'method' then - simpleList = buildSimpleList(obj.parent, max) - end - return simpleList -end - ----Create a new status ----@param parentStatus core.guide.status ----@param interface table ----@param deep boolean ----@return core.guide.status -function m.status(parentStatus, interface, deep) - ---@class core.guide.status - local status = { - share = parentStatus and parentStatus.share or { - count = 0, - cacheLock = {}, - }, - depth = parentStatus and (parentStatus.depth + 1) or 0, - searchDeep= parentStatus and parentStatus.searchDeep or deep or -999, - interface = parentStatus and parentStatus.interface or {}, - deep = parentStatus and parentStatus.deep, - clock = parentStatus and parentStatus.clock or osClock(), - results = {}, - } - if interface then - for k, v in pairs(interface) do - status.interface[k] = v - end - end - status.deep = status.depth <= status.searchDeep - return status -end - -function m.copyStatusResults(a, b) - local ra = a.results - local rb = b.results - for i = 1, #rb do - ra[#ra+1] = rb[i] - end -end - -function m.isGlobal(source) - if source.type == 'setglobal' - or source.type == 'getglobal' then - if source.node and source.node.tag == '_ENV' then - return true - end - end - if source.type == 'field' then - source = source.parent - end - if source.type == 'getfield' - or source.type == 'setfield' then - local node = source.node - if node and node.special == '_G' then - return true - end - end - return false -end - -function m.isDoc(source) - return source.type:sub(1, 4) == 'doc.' -end - -function m.isDocClass(source) - return source.type == 'doc.class' -end - -function m.isSet(source) - local tp = source.type - if tp == 'setglobal' - or tp == 'local' - or tp == 'setlocal' - or tp == 'setfield' - or tp == 'setmethod' - or tp == 'setindex' - or tp == 'tablefield' - or tp == 'tableindex' then - return true - end - if tp == 'call' then - local special = m.getSpecial(source.node) - if special == 'rawset' then - return true - end - end - return false -end - -function m.isGet(source) - local tp = source.type - if tp == 'getglobal' - or tp == 'getlocal' - or tp == 'getfield' - or tp == 'getmethod' - or tp == 'getindex' then - return true - end - if tp == 'call' then - local special = m.getSpecial(source.node) - if special == 'rawget' then - return true - end - end - return false -end - -function m.getSpecial(source) - if not source then - return nil - end - return source.special -end - ---- 根据函数的调用参数,获取:调用,参数索引 -function m.getCallAndArgIndex(callarg) - local callargs = callarg.parent - if not callargs or callargs.type ~= 'callargs' then - return nil - end - local index - for i = 1, #callargs do - if callargs[i] == callarg then - index = i - break - end - end - local call = callargs.parent - return call, index -end - ---- 根据函数调用的返回值,获取:调用的函数,参数列表,自己是第几个返回值 -function m.getCallValue(source) - local value = m.getObjectValue(source) or source - if not value then - return - end - local call, index - if value.type == 'call' then - call = value - index = 1 - elseif value.type == 'select' then - call = value.vararg - index = value.index - if call.type ~= 'call' then - return - end - else - return - end - if call.node.special == 'pcall' - or call.node.special == 'xpcall' then - return call.args and call.args[1], call.args, index - 1 - end - return call.node, call.args, index -end - -function m.getNextRef(ref) - local nextRef = ref.next - if nextRef then - if nextRef.type == 'setfield' - or nextRef.type == 'getfield' - or nextRef.type == 'setmethod' - or nextRef.type == 'getmethod' - or nextRef.type == 'setindex' - or nextRef.type == 'getindex' then - return nextRef - end - end - -- 穿透 rawget 与 rawset - local call, index = m.getCallAndArgIndex(ref) - if call then - if call.node.special == 'rawset' and index == 1 then - return call - end - if call.node.special == 'rawget' and index == 1 then - return call - end - end - - return nil -end - -function m.checkSameSimpleInValueOfTable(status, value, start, pushQueue) - if value.type ~= 'table' then - return - end - for i = 1, #value do - local field = value[i] - pushQueue(field, start + 1) - end -end - -function m.checkStatusDepth(status) - if status.depth <= 20 then - return true - end - if m.debugMode then - error('status.depth overflow') - elseif DEVELOP then - --log.warn(debug.traceback('status.depth overflow')) - logWarn('status.depth overflow') - end - return false -end - -function m.searchFields(status, obj, key) - if not m.checkStatusDepth(status) then - return - end - local simple = m.getSimple(obj) - if not simple then - return - end - simple[#simple+1] = key or m.ANY - m.searchSameFields(status, simple, 'field') - m.cleanResults(status.results) -end - -function m.searchDefFields(status, obj, key) - if not m.checkStatusDepth(status) then - return - end - local simple = m.getSimple(obj) - if not simple then - return - end - simple[#simple+1] = key or m.ANY - m.searchSameFields(status, simple, 'deffield') - m.cleanResults(status.results) -end - ----@param obj parser.guide.object ----@return parser.guide.object -function m.getObjectValue(obj) - while obj.type == 'paren' do - obj = obj.exp - if not obj then - return nil - end - end - if obj.type == 'boolean' - or obj.type == 'number' - or obj.type == 'integer' - or obj.type == 'string' - or obj.type == 'doc.type.table' - or obj.type == 'doc.type.arrary' then - return obj - end - if obj.value then - return obj.value - end - if obj.type == 'field' - or obj.type == 'method' then - return obj.parent and obj.parent.value - end - if obj.type == 'call' then - if obj.node.special == 'rawset' then - return obj.args and obj.args[3] - else - return obj - end - end - if obj.type == 'select' then - return obj - end - return nil -end - -function m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) - local cache, makeCache = m.getRefCache(status, mt, '__index') - if cache then - for _, obj in ipairs(cache) do - pushQueue(obj, start, true) - end - return - end - cache = {} - local newStatus = m.status(status) - m.searchDefFields(newStatus, mt, '__index') - local refsStatus = m.status(status) - for i = 1, #newStatus.results do - local indexValue = m.getObjectValue(newStatus.results[i]) - if indexValue then - m.searchRefs(refsStatus, indexValue, 'ref') - end - end - for i = 1, #refsStatus.results do - local obj = refsStatus.results[i] - pushQueue(obj, start, true) - cache[i] = obj - end - if makeCache then - makeCache(cache) - end -end -function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) - if not func or func.special ~= 'setmetatable' then - return - end - local call = func.parent - local args = call.args - if not args then - return - end - local obj = args[1] - local mt = args[2] - if obj then - pushQueue(obj, start, true) - local newStatus = m.status(status) - m.searchRefs(newStatus, obj, 'def') - for _, def in ipairs(newStatus.results) do - pushQueue(def, start, true) - end - end - if mt then - if not status.share.markMetaTable then - status.share.markMetaTable = {} - end - if status.share.markMetaTable[mt] then - return - end - status.share.markMetaTable[mt] = true - m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) - status.share.markMetaTable[mt] = nil - end -end - -function m.checkSameSimpleInValueOfCallMetaTable(status, call, start, pushQueue) - if status.crossMetaTableMark then - return - end - status.crossMetaTableMark = true - if call.type == 'call' then - m.checkSameSimpleInValueOfSetMetaTable(status, call.node, start, pushQueue) - end - status.crossMetaTableMark = false -end - -function m.checkSameSimpleInSpecialBranch(status, obj, start, pushQueue) - if status.interface.index then - local results = status.interface.index(obj) - if not results then - return - end - for _, res in ipairs(results) do - pushQueue(obj, start + 1) - end - end -end - -function m.checkSameSimpleInParamSelf(status, obj, start, pushQueue) - if obj.type ~= 'getlocal' or obj[1] ~= 'self' then - return - end - local node = obj.node - if node.tag == 'self' then - return - end - if node.parent.type ~= 'funcargs' then - return - end - local func = node.parent.parent - if func.type ~= 'function' or func.parent.type ~= 'setfield' then - return - end - local fieldNode = func.parent.node - local newStatus = m.status(status) - m.searchRefs(newStatus, fieldNode, 'ref') - for _, ref in ipairs(newStatus.results) do - pushQueue(ref, start, true) - end -end - -local function appendValidGenericType(results, status, typeName, obj) - if typeName.parent.type == 'doc.type.typeliteral' then - if obj.type == 'string' and status.interface.docType then - local docs = status.interface.docType(obj[1]) - for i = 1, #docs do - local doc = docs[i] - if doc.type == 'doc.class.name' - or doc.type == 'doc.alias.name' then - results[#results+1] = doc - break - end - end - end - else - -- 发现没有使用 `T`,则沿用既有逻辑直接返回实参 - results[#results+1] = obj - end -end - -local function stepRefOfGenericCrossTable(status, doc, typeName) - for _, typeUnit in ipairs(doc.extends.types) do - if typeUnit.type == 'doc.type.table' then - for _, where in ipairs {'key', 'value'} do - local childTypes = typeUnit[where].types - for _, childName in ipairs(childTypes) do - if childName[1] == typeName[1] then - return function (obj) - local childStatus = m.status(status) - m.searchRefs(childStatus, obj, 'def') - for _, res in ipairs(childStatus.results) do - if res.type == 'doc.type.table' then - return res[where] - end - if res.type == 'doc.type.array' then - if where == 'key' then - return status.interface and status.interface.docType('integer')[1] - end - if where == 'value' then - return res.node - end - end - end - end - end - end - end - return function (obj) - return nil - end - elseif typeUnit.type == 'doc.type.array' then - return function (obj) - local childStatus = m.status(status) - m.searchRefs(childStatus, obj, 'def') - for _, res in ipairs(childStatus.results) do - if res.type == 'doc.type.array' then - return res.node - end - end - end - end - end - return nil -end - -local function getIteratorArg(status, args, index) - local call = args.parent - local node = call.node - if not node.iterator then - return nil - end - if node.type ~= 'call' then - return nil - end - local results = m.checkSameSimpleInCallInSameFile(status, node.node, node.args, index + 1) - return results[1] -end - -local function stepRefOfGeneric(status, typeUnit, args, mode) - local results = {} - if not args then - return results - end - local myName = typeUnit[1] - for _, typeName in ipairs(typeUnit.typeGeneric[myName]) do - if typeName == typeUnit then - goto CONTINUE - end - local docArg = m.getParentType(typeName, 'doc.type.arg') - or m.getParentType(typeName, 'doc.param') - or m.getParentType(typeName, 'doc.type.array') - if not docArg then - goto CONTINUE - end - local doc = m.getDocState(docArg) - if not doc.bindSources then - goto CONTINUE - end - local crossTable = stepRefOfGenericCrossTable(status, docArg, typeName) - - -- find out param index - local genericIndex - if docArg.type == 'doc.param' then - local paramName = docArg.param[1] - for _, source in ipairs(doc.bindSources) do - if source.type == 'function' - and source.args then - for i, arg in ipairs(source.args) do - if arg[1] == paramName then - genericIndex = i - break - end - end - end - end - elseif docArg.type == 'doc.type.arg' then - for index, arg in ipairs(docArg.parent.args) do - if arg == docArg then - genericIndex = index - break - end - end - end - - local callArg = args[genericIndex] - or getIteratorArg(status, args, genericIndex) - - if not callArg then - goto CONTINUE - end - if crossTable then - callArg = crossTable(callArg) - if not callArg then - goto CONTINUE - end - end - appendValidGenericType(results, status, typeName, callArg) - ::CONTINUE:: - end - return results -end - -function m.checkSameSimpleByDocType(status, doc, args) - if status.share.searchingBindedDoc then - return - end - if doc.type ~= 'doc.type' then - return - end - local results = {} - for _, piece in ipairs(doc.types) do - if piece.typeGeneric then - local pieceResult = stepRefOfGeneric(status, piece, args, 'def') - for _, res in ipairs(pieceResult) do - results[#results+1] = res - end - else - local pieceResult = stepRefOfDocType(status, piece, 'def') - for _, res in ipairs(pieceResult) do - results[#results+1] = res - end - end - end - return results -end - -function m.checkSameSimpleByBindDocs(status, obj, start, pushQueue, mode) - if not obj.bindDocs then - return - end - if status.share.searchingBindedDoc then - return - end - local skipInfer = false - local results = {} - for _, doc in ipairs(obj.bindDocs) do - if doc.type == 'doc.class' then - results[#results+1] = doc - elseif doc.type == 'doc.type' then - results[#results+1] = doc - elseif doc.type == 'doc.param' then - -- function (x) 的情况 - if obj.type == 'local' - and m.getKeyName(obj) == doc.param[1] then - if obj.parent.type == 'funcargs' - or obj.parent.type == 'in' - or obj.parent.type == 'loop' then - results[#results+1] = doc.extends - end - end - elseif doc.type == 'doc.field' then - results[#results+1] = doc - elseif doc.type == 'doc.vararg' then - if obj.type == '...' then - results[#results+1] = doc - end - end - end - for _, res in ipairs(results) do - if res.type == 'doc.class' - or res.type == 'doc.type' - or res.type == 'doc.vararg' then - pushQueue(res, start, true) - skipInfer = true - end - if res.type == 'doc.type.function' then - pushQueue(res, start, true) - elseif res.type == 'doc.field' then - pushQueue(res, start + 1) - end - end - return skipInfer -end - -function m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) - if status.share.searchingBindedDoc then - return - end - if not obj.bindSources then - return - end - status.share.searchingBindedDoc = true - local mark = {} - local newStatus = m.status(status) - for _, ref in ipairs(obj.bindSources) do - if not mark[ref] then - mark[ref] = true - m.searchRefs(newStatus, ref, mode) - end - end - status.share.searchingBindedDoc = nil - for _, res in ipairs(newStatus.results) do - pushQueue(res, start, true) - end -end - -function m.checkSameSimpleOfRefByDocReturn(status, obj, start, pushQueue, mode) - if status.share.searchingBindedDoc then - return - end - if not obj.bindSources then - return - end - local index = 0 - for _, doc in ipairs(obj.bindGroup) do - if doc.type == 'doc.return' then - index = index + 1 - if doc == obj then - break - end - end - end - status.share.searchingBindedDoc = true - local mark = {} - local newStatus = m.status(status) - for _, ref in ipairs(obj.bindSources) do - if not mark[ref] then - mark[ref] = true - m.searchRefs(newStatus, ref, mode) - end - end - status.share.searchingBindedDoc = nil - for _, res in ipairs(newStatus.results) do - local call = res.parent - if call.type == 'call' then - if index == 1 then - local sel = call.parent - if sel.type == 'select' and sel.index == index then - pushQueue(sel.parent, start, true) - end - else - if call.extParent then - for _, sel in ipairs(call.extParent) do - if sel.type == 'select' and sel.index == index then - pushQueue(sel.parent, start, true) - end - end - end - end - end - end -end - -local function getArrayOrTableLevel(obj) - local level = 0 - while true do - local parent = obj.parent - if parent.type == 'doc.type.array' then - level = level + 1 - elseif parent.type == 'doc.type.table' then - if obj.type == 'doc.type' then - level = level + 1 - -- else 只存在 obj.type == 'doc.type.name' 的情况,即 table<k,v> 中的 table,这种是不需要再增加层级的 - end - elseif parent.type == 'doc.type' and parent.parent and parent.parent.type == 'doc.type.table' then - level = level + 1 - parent = parent.parent - else - break - end - obj = parent - end - return level -end - -function m.checkSameSimpleByDoc(status, obj, start, pushQueue, mode) - if obj.type == 'doc.class.name' - or obj.type == 'doc.class' then - if obj.type == 'doc.class.name' then - obj = m.getDocState(obj) - end - local classStart - for _, doc in ipairs(obj.bindGroup) do - if doc == obj then - classStart = true - elseif doc.type == 'doc.class' then - classStart = false - end - if classStart and doc.type == 'doc.field' then - pushQueue(doc, start + 1) - end - end - m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) - if mode == 'ref' then - local pieceResult = stepRefOfDocType(status, obj.class, 'ref') - for _, res in ipairs(pieceResult) do - pushQueue(res, start, true) - end - if obj.extends then - for _, ext in ipairs(obj.extends) do - local pieceResult = stepRefOfDocType(status, ext, 'def') - for _, res in ipairs(pieceResult) do - pushQueue(res, start, true) - end - end - end - end - return true - elseif obj.type == 'doc.type' then - for _, piece in ipairs(obj.types) do - local pieceResult = stepRefOfDocType(status, piece, 'def') - for _, res in ipairs(pieceResult) do - pushQueue(res, start, true) - end - end - if mode == 'ref' then - m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) - end - return true - elseif obj.type == 'doc.type.name' - or obj.type == 'doc.see.name' then - local pieceResult = stepRefOfDocType(status, obj, 'def') - for _, res in ipairs(pieceResult) do - pushQueue(res, start, true) - end - - if mode == 'ref' then - local state = m.getDocState(obj) - if state.type == 'doc.type' then - m.checkSameSimpleOfRefByDocSource(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode) - end - if state.type == 'doc.return' then - m.checkSameSimpleOfRefByDocReturn(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode) - end - end - return true - elseif obj.type == 'doc.field' then - if mode ~= 'field' - and mode ~= 'deffield' then - return m.checkSameSimpleByDoc(status, obj.extends, start, pushQueue, mode) - end - elseif obj.type == 'doc.type.array' then - pushQueue(obj.node, start + 1, true) - return true - elseif obj.type == 'doc.type.table' then - pushQueue(obj.node, start, true) - pushQueue(obj.value, start + 1, true) - return true - elseif obj.type == 'doc.vararg' then - pushQueue(obj.vararg, start, true) - end -end - -function m.checkSameSimpleInArg1OfSetMetaTable(status, obj, start, pushQueue) - local args = obj.parent - if not args or args.type ~= 'callargs' then - return - end - local callNode = args.parent.node - if callNode.special ~= 'setmetatable' then - return - end - if args[1] ~= obj then - return - end - local mt = args[2] - if mt then - if m.hasValueMark(status, mt) then - return - end - m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) - end -end - -function m.searchSameMethodOutSelf(ref, mark) - local selfNode - if ref.tag == 'self' then - selfNode = ref - else - if ref.type == 'getlocal' - or ref.type == 'setlocal' then - local node = ref.node - if node.tag == 'self' then - selfNode = node - end - end - end - if selfNode then - if mark[selfNode] then - return nil - end - mark[selfNode] = true - local method = selfNode.method.node - if mark[method] then - return nil - end - mark[method] = true - return method - end -end - -function m.searchSameMethodIntoSelf(ref, mark) - local nxt = ref.next - if not nxt then - return nil - end - if nxt.type ~= 'setmethod' then - return nil - end - if mark[ref] then - return nil - end - mark[ref] = true - local value = nxt.value - if not value or value.type ~= 'function' then - return nil - end - local selfRef = value.locals and value.locals[1] - if not selfRef or selfRef.tag ~= 'self' then - return nil - end - if mark[selfRef] then - return nil - end - mark[selfRef] = true - return selfRef -end - -function m.searchSameFieldsCrossMethod(status, ref, start, pushQueue, mode) - if status.share.crossMethodMark2 then - return - end - local mark = status.crossMethodMark - if not mark then - mark = {} - status.crossMethodMark = mark - end - if mark[ref] then - return - end - local selfRef = m.searchSameMethodIntoSelf(ref, mark) - if selfRef then - tracy.ZoneBeginN 'searchSameFieldsCrossMethod' - local _ <close> = tracy.ZoneEnd - -- 如果自己是method,则只检查自己内部的self引用 - status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1 - status.share.crossMethodMark2 = true - local newStatus = m.status(status) - m.searchRefs(newStatus, selfRef, mode) - for _, res in ipairs(newStatus.results) do - pushQueue(res, start, true) - end - status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1 - status.share.crossMethodMark2 = nil - return - end - local method = m.searchSameMethodOutSelf(ref, mark) - if method then - pushQueue(method, start, true) - return - end -end - -local function checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, source, index, args) - source = m.getObjectValue(source) or source - if not source or source.type ~= 'function' then - return - end - if not source.bindDocs then - return - end - local returns = {} - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.return' then - for _, rtn in ipairs(doc.returns) do - returns[#returns+1] = rtn - end - end - end - local rtn = returns[index] - if not rtn then - return - end - local types = m.checkSameSimpleByDocType(status, rtn, args) - if not types then - return - end - for _, res in ipairs(types) do - results[#results+1] = res - end - return true -end - -local function checkSameSimpleAndMergeDocFunctionReturn(status, results, docFunc, index, args) - if docFunc.type ~= 'doc.type.function' then - return - end - local rtn = docFunc.returns[index] - if rtn then - local types = m.checkSameSimpleByDocType(status, rtn, args) - if types then - for _, res in ipairs(types) do - results[#results+1] = res - end - return true - end - end -end - -local function checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, source, index) - if not source.bindDocs then - return - end - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.type' then - for _, typeUnit in ipairs(doc.types) do - if checkSameSimpleAndMergeDocFunctionReturn(status, results, typeUnit, index) then - return true - end - end - end - end -end - -function m.checkSameSimpleInCallInSameFile(status, func, args, index) - if not status.share.callResultsCache then - status.share.callResultsCache = {} - end - local cache = status.share.callResultsCache[func] - if not cache then - cache = {} - status.share.callResultsCache[func] = cache - end - local results = cache[index] - if results then - return results - end - results = {} - if func.special then - --return results - end - local newStatus = m.status(status) - m.searchRefs(newStatus, func, 'def') - local hasDocReturn - for _, def in ipairs(newStatus.results) do - hasDocReturn = checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, def, index) - or checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, def, index, args) - or checkSameSimpleAndMergeDocFunctionReturn(status, results, def, index, args) - or hasDocReturn - end - if not hasDocReturn then - for _, def in ipairs(newStatus.results) do - local value = m.getObjectValue(def) or def - if value.type == 'function' then - local returns = value.returns - if returns then - for _, ret in ipairs(returns) do - local exp = ret[index] - if exp then - results[#results+1] = exp - end - end - end - end - end - cache[index] = results - end - return results -end - -function m.checkSameSimpleInCall(status, ref, start, pushQueue, mode) - if status.share.inBeSetValue and status.share.inBeSetValue > 0 then - return - end - if status.share.inSetValue and status.share.inSetValue > 5 then - return - end - local func, args, index = m.getCallValue(ref) - if not func then - return - end - if m.checkCallMark(status, func.parent, true) then - return - end - status.share.inSetValue = (status.share.inSetValue or 0) + 1 - -- 检查赋值是 semetatable() 的情况 - m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) - local objs = m.checkSameSimpleInCallInSameFile(status, func, args, index) - if status.interface.call then - local cobjs = status.interface.call(func, args, index) - if cobjs then - for _, obj in ipairs(cobjs) do - if not m.checkReturnMark(status, obj) then - objs[#objs+1] = obj - end - end - end - end - m.cleanResults(objs) - if not status.share.callFuncMark then - status.share.callFuncMark = {} - end - local mark = {} - for _, obj in ipairs(objs) do - if mark[obj] then - goto CONTINUE - end - if status.share.callFuncMark[obj] then - goto CONTINUE - end - status.share.callFuncMark[obj] = true - local newStatus = m.status(status) - m.searchRefs(newStatus, obj, mode) - pushQueue(obj, start, true) - mark[obj] = true - for _, obj in ipairs(newStatus.results) do - pushQueue(obj, start, true) - mark[obj] = true - end - status.share.callFuncMark[obj] = nil - ::CONTINUE:: - end - status.share.inSetValue = (status.share.inSetValue or 0) - 1 -end - -local function searchRawset(ref, results) - if m.getKeyName(ref) ~= 'rawset' then - return - end - local call = ref.parent - if call.type ~= 'call' or call.node ~= ref then - return - end - if not call.args then - return - end - local arg1 = call.args[1] - if arg1.special ~= '_G' then - -- 不会吧不会吧,不会真的有人写成 `rawset(_G._G._G, 'xxx', value)` 吧 - return - end - results[#results+1] = call -end - -local function searchG(ref, results) - while ref and m.getKeyName(ref) == '_G' do - results[#results+1] = ref - ref = ref.next - end - if ref then - results[#results+1] = ref - searchRawset(ref, results) - end -end - -local function searchEnvRef(ref, results) - if ref.type == 'setglobal' - or ref.type == 'getglobal' then - results[#results+1] = ref - searchG(ref, results) - elseif ref.type == 'getlocal' then - results[#results+1] = ref.next - searchG(ref.next, results) - end -end - -function m.findGlobals(ast) - local root = m.getRoot(ast) - local results = {} - local env = m.getENV(root) - if env.ref then - for _, ref in ipairs(env.ref) do - searchEnvRef(ref, results) - end - end - return results -end - -function m.findGlobalsOfName(ast, name) - local root = m.getRoot(ast) - local results = {} - local globals = m.findGlobals(root) - for _, global in ipairs(globals) do - if m.getKeyName(global) == name then - results[#results+1] = global - end - end - return results -end - -function m.checkSameSimpleInGlobal(status, source) - local name = m.getKeyName(source) - if not name then - return - end - local objs - if status.interface.global then - objs = status.interface.global(name) - else - objs = m.findGlobalsOfName(source, name) - end - return objs -end - -function m.hasValueMark(status, value) - if not status.share.valueMark then - status.share.valueMark = {} - end - if status.share.valueMark[value] then - return true - end - status.share.valueMark[value] = true - return false -end - -function m.checkCallMark(status, a, mark) - if not status.share.callMark then - status.share.callMark = {} - end - if mark then - status.share.callMark[a] = mark - else - return status.share.callMark[a] - end - return false -end - -function m.checkReturnMark(status, a, mark) - if not status.share.returnMark then - status.share.returnMark = {} - end - local result = status.share.returnMark[a] - if mark then - status.share.returnMark[a] = mark - end - return result -end - -function m.searchSameFieldsInValue(status, ref, start, pushQueue, mode) - if status.share.inBeSetValue and status.share.inBeSetValue > 0 then - return - end - if status.share.inSetValue and status.share.inSetValue > 5 then - return - end - local value = m.getObjectValue(ref) - if not value then - return - end - if m.hasValueMark(status, value) then - return - end - status.share.inSetValue = (status.share.inSetValue or 0) + 1 - if not status.share.tempValueMark then - status.share.tempValueMark = {} - end - if not status.share.tempValueMark[value] then - status.share.tempValueMark[value] = true - local newStatus = m.status(status) - m.searchRefs(newStatus, value, mode) - status.share.tempValueMark[value] = nil - for _, res in ipairs(newStatus.results) do - pushQueue(res, start, true) - end - pushQueue(value, start, true) - end - status.share.inSetValue = (status.share.inSetValue or 0) - 1 - -- 检查形如 a = f() 的分支情况 - m.checkSameSimpleInCall(status, value, start, pushQueue, mode) -end - -function m.checkSameSimpleAsTableField(status, ref, start, pushQueue) - if not status.deep then - --return - end - local parent = ref.parent - if not parent or parent.type ~= 'tablefield' then - return - end - if m.hasValueMark(status, ref) then - return - end - local newStatus = m.status(status) - m.searchRefs(newStatus, parent.field, 'ref') - for _, res in ipairs(newStatus.results) do - pushQueue(res, start, true) - end -end - -function m.checkSearchLevel(status) - status.share.back = status.share.back or 0 - if status.share.back >= (status.interface.searchLevel or 0) then - -- TODO 限制向前搜索的次数 - --return true - end - status.share.back = status.share.back + 1 - return false -end - -function m.checkSameSimpleAsReturn(status, ref, start, pushQueue) - if not status.deep then - return - end - if not ref.parent or ref.parent.type ~= 'return' then - return - end - if ref.parent.parent.type ~= 'main' then - return - end - if m.checkSearchLevel(status) then - return - end - local newStatus = m.status(status) - m.searchRefsAsFunctionReturn(newStatus, ref, 'ref') - for _, res in ipairs(newStatus.results) do - if not m.checkCallMark(status, res) then - pushQueue(res, start, true) - end - end -end - -function m.checkSameSimpleAsSetValue(status, ref, start, pushQueue) - if not status.deep then - --return - end - if status.share.inSetValue and status.share.inSetValue > 0 then - return - end - if status.share.inBeSetValue and status.share.inBeSetValue > 5 then - return - end - if ref.type == 'select' then - return - end - local parent = ref.parent - if not parent then - return - end - if m.getObjectValue(parent) ~= ref then - return - end - if m.hasValueMark(status, ref) then - return - end - if m.checkSearchLevel(status) then - return - end - local obj - if parent.type == 'local' - or parent.type == 'setglobal' - or parent.type == 'setlocal' then - obj = parent - elseif parent.type == 'setfield' then - obj = parent.field - elseif parent.type == 'setmethod' then - obj = parent.method - end - if not obj then - return - end - status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1 - local newStatus = m.status(status) - m.searchRefs(newStatus, obj, 'ref') - for _, res in ipairs(newStatus.results) do - pushQueue(res, start, true) - end - status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1 -end - -local function getTableAndIndexIfIsForPairsKeyOrValue(ref) - if ref.type ~= 'local' then - return - end - - if not ref.parent or ref.parent.type ~= 'in' then - return - end - - if not ref.value or ref.value.type ~= 'select' then - return - end - - local rootSelectObj = ref.value - if rootSelectObj.index ~= 1 and rootSelectObj.index ~= 2 then - return - end - - if not rootSelectObj.vararg or rootSelectObj.vararg.type ~= 'call' then - return - end - local rootCallObj = rootSelectObj.vararg - - if not rootCallObj.node or rootCallObj.node.type ~= 'call' then - return - end - local pairsCallObj = rootCallObj.node - - if not pairsCallObj.node - or (pairsCallObj.node.special ~= 'pairs' and pairsCallObj.node.special ~= 'ipairs') then - return - end - - if not pairsCallObj.args or not pairsCallObj.args[1] then - return - end - local tableObj = pairsCallObj.args[1] - - return tableObj, rootSelectObj.index -end - -function m.checkSameSimpleAsKeyOrValueInForParis(status, ref, start, pushQueue) - local tableObj, index = getTableAndIndexIfIsForPairsKeyOrValue(ref) - if not tableObj then - return - end - - local newStatus = m.status(status) - m.searchRefs(newStatus, tableObj, 'def') - for _, def in ipairs(newStatus.results) do - if def.bindDocs then - for _, binddoc in ipairs(def.bindDocs) do - if binddoc.type == 'doc.type' then - if binddoc.types[1] and binddoc.types[1].type == 'doc.type.table' then - if index == 1 then - pushQueue(binddoc.types[1].key, start, true) - elseif index == 2 then - pushQueue(binddoc.types[1].value, start, true) - end - end - end - end - end - end -end - ---- ----@param func parser.guide.object ----@param argIndex integer ----@return integer? -local function findGenericFromArgIndexToReturnIndex(func, argIndex) - if not func.bindDocs then - return nil - end - local paramType - for _, doc in ipairs(func.bindDocs) do - if doc.type == 'doc.param' then - if doc.extends and doc.extends.paramIndex == argIndex then - paramType = doc.extends - break - end - end - end - if not paramType then - return nil - end - for _, typeUnit in ipairs(paramType.types) do - if typeUnit.typeGeneric then - local generic = typeUnit.typeGeneric[typeUnit[1]] - if generic then - for _, typeName in ipairs(generic) do - local docType = typeName.parent - if docType.returnIndex then - return docType.returnIndex - end - end - end - end - end - return nil -end - -function m.checkSameSimpleAsCallArg(status, ref, start, pushQueue) - if not status.deep then - return - end - local call, index = m.getCallAndArgIndex(ref) - if not call then - return - end - if call.parent.type ~= 'select' then - return - end - if (status.share.inSetValue or 0) > 0 then - return - end - if status.share.inBeSetValue and status.share.inBeSetValue > 5 then - return - end - status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1 - local newStatus = m.status(status) - m.searchRefs(newStatus, call.node, 'def') - for _, func in ipairs(newStatus.results) do - local rindex = findGenericFromArgIndexToReturnIndex(func, index) - if rindex then - if rindex == 1 then - pushQueue(call.parent.parent, start, true) - else - if call.extParent then - for _, slt in ipairs(call.extParent) do - if slt.index == rindex then - pushQueue(slt.parent, start, true) - end - end - end - end - end - end - status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1 -end - -local function hasTypeName(doc, name) - if doc.type == 'doc.type' then - for _, tunit in ipairs(doc.types) do - if tunit.type == 'doc.type.name' - and tunit[1] == name then - return true - end - end - end - if doc.type == 'doc.type.name' - or doc.type == 'doc.class.name' then - if doc[1] == name then - return true - end - end - return false -end - -function m.checkSameSimpleInString(status, ref, start, pushQueue, mode) - -- 特殊处理 ('xxx').xxx 的形式 - if ref.type ~= 'string' - and not hasTypeName(ref, 'string') then - return - end - if not status.interface.docType then - return - end - if status.share.searchingBindedDoc then - return - end - if not status.share.markString then - status.share.markString = {} - end - local marked = ref - if ref.type == 'string' then - marked = 'string' - end - if status.share.markString[marked] then - return - end - status.share.markString[marked] = true - local newStatus = m.status(status) - local docs = status.interface.docType('stringlib') - local mark = {} - for i = 1, #docs do - local doc = docs[i] - m.searchFields(newStatus, doc) - end - for _, res in ipairs(newStatus.results) do - if mark[res] then - goto CONTINUE - end - mark[res] = true - pushQueue(res, start + 1) - ::CONTINUE:: - end - status.share.markString[marked] = nil - return true -end - +-- TODO: compatible +m.getRoot = guide.getRoot +m.eachSource = guide.eachSource +m.eachSourceType = guide.eachSourceType +m.eachSourceContain = guide.eachSourceContain +m.eachSourceBetween = guide.eachSourceBetween +m.getStartFinish = guide.getStartFinish +m.isLiteral = guide.isLiteral + +---@alias guide.searchmode '"ref"'|'"def"'|'"field"' + +---添加结果 +---@param status guide.status +---@param mode guide.searchmode +---@param ref parser.guide.object +---@param simple table function m.pushResult(status, mode, ref, simple) local results = status.results if mode == 'def' then @@ -2836,7 +109,7 @@ function m.pushResult(status, mode, ref, simple) if ref.parent and ref.parent.type == 'return' then results[#results+1] = ref end - if m.isLiteral(ref) + if guide.isLiteral(ref) and ref.parent and ref.parent.type == 'callargs' and ref ~= simple.node then @@ -2890,1948 +163,91 @@ function m.pushResult(status, mode, ref, simple) end end -function m.checkSameSimpleName(ref, sm) - if sm == m.ANY then - return true - end - - if m.getSimpleName(ref) == sm then - return true - end - if ref.type == 'doc.type' - and ref.array == true then - return true - end - return false -end - -function m.isValidSetRef(ref) - if m.isSet(ref) then - return true - end -end - -function m.checkSameSimple(status, simple, ref, start, force, mode, pushQueue) - if start > #simple then - return - end - for i = start, #simple do - local sm = simple[i] - if not force and not m.checkSameSimpleName(ref, sm) then - return - end - force = false - local cmode = mode - local skipSearch - if i < #simple then - cmode = 'ref' - else - if mode == 'deffield' then - if not m.isSet(ref) then - skipSearch = true - end - end - end - -- 检查 doc - local skipInfer = m.checkSameSimpleByBindDocs(status, ref, i, pushQueue, cmode) - or m.checkSameSimpleByDoc(status, ref, i, pushQueue, cmode) - -- 检查自己是字符串的分支情况 - m.checkSameSimpleInString(status, ref, i, pushQueue, cmode) - if not skipInfer and not skipSearch then - -- 穿透 self:func 与 mt:func - m.searchSameFieldsCrossMethod(status, ref, i, pushQueue, cmode) - -- 穿透赋值 - m.searchSameFieldsInValue(status, ref, i, pushQueue, cmode) - -- 检查自己是字面量表的情况 - m.checkSameSimpleInValueOfTable(status, ref, i, pushQueue) - -- 检查自己作为 setmetatable 第一个参数的情况 - m.checkSameSimpleInArg1OfSetMetaTable(status, ref, i, pushQueue) - -- 检查自己作为 setmetatable 调用的情况 - m.checkSameSimpleInValueOfCallMetaTable(status, ref, i, pushQueue) - -- 检查自己是特殊变量的分支的情况 - m.checkSameSimpleInSpecialBranch(status, ref, i, pushQueue) - -- self 的特殊处理 - m.checkSameSimpleInParamSelf(status, ref, i, pushQueue) - -- 自己是 call 的情况 - m.checkSameSimpleInCall(status, ref, i, pushQueue, cmode) - if cmode == 'ref' then - -- 检查形如 { a = f } 的情况 - m.checkSameSimpleAsTableField(status, ref, i, pushQueue) - -- 检查形如 return m 的情况 - m.checkSameSimpleAsReturn(status, ref, i, pushQueue) - -- 检查形如 a = f 的情况 - m.checkSameSimpleAsSetValue(status, ref, i, pushQueue) - -- 检查形如 for k,v in pairs()/ipairs() do end 的情况 - m.checkSameSimpleAsKeyOrValueInForParis(status, ref, i, pushQueue) - -- 检查自己是函数参数的情况(泛型) local x = call(V) - m.checkSameSimpleAsCallArg(status, ref, i, pushQueue) - end - end - if i == #simple then - break - end - ref = m.getNextRef(ref) - if not ref then - return - end - end - m.pushResult(status, mode, ref, simple) - local value = m.getObjectValue(ref) - if value then - m.pushResult(status, mode, value, simple) - end -end - -local queuesPool = {} -local startsPool = {} -local forcesPool = {} -local poolSize = 0 - -local function allocQueue() - if poolSize <= 0 then - return {}, {}, {} - else - local queues = queuesPool[poolSize] - local starts = startsPool[poolSize] - local forces = forcesPool[poolSize] - poolSize = poolSize - 1 - return queues, starts, forces - end -end - -local function deallocQueue(queues, starts, forces) - poolSize = poolSize + 1 - queuesPool[poolSize] = queues - startsPool[poolSize] = starts - forcesPool[poolSize] = forces -end - -function m.searchSameFields(status, simple, mode) - local queues, starts, forces = allocQueue() - local queueLen = 0 - local locks = {} - local function appendQueue(obj, start, force) - local lock = locks[start] - if not lock then - lock = {} - locks[start] = lock - end - if lock[obj] then - return - end - lock[obj] = true - queueLen = queueLen + 1 - queues[queueLen] = obj - starts[queueLen] = start - forces[queueLen] = force - if obj.mirror then - if not lock[obj.mirror] then - lock[obj.mirror] = true - queueLen = queueLen + 1 - queues[queueLen] = obj.mirror - starts[queueLen] = start - forces[queueLen] = force - end - end - end - local function pushQueue(obj, start, force) - if obj.type == 'getlocal' - or obj.type == 'setlocal' then - obj = obj.node - end - appendQueue(obj, start, force) - if obj.type == 'local' and obj.ref then - for _, ref in ipairs(obj.ref) do - appendQueue(ref, start, force) - end - end - if m.isGlobal(obj) then - local refs = m.checkSameSimpleInGlobal(status, obj) - if refs then - for _, ref in ipairs(refs) do - appendQueue(ref, start, force) - end - end - end - end - if simple.mode == 'global' then - -- 全局变量开头 - pushQueue(simple.node, 1) - elseif simple.mode == 'local' then - -- 局部变量开头 - pushQueue(simple.node, 1) - else - pushQueue(simple.node, 1) - end - local max = 0 - for i = 1, 1e6 do - if queueLen <= 0 then - break - end - local obj = queues[queueLen] - local start = starts[queueLen] - local force = forces[queueLen] - queues[queueLen] = nil - starts[queueLen] = nil - forces[queueLen] = nil - queueLen = queueLen - 1 - max = max + 1 - status.share.count = status.share.count + 1 - if status.share.count % 10000 == 0 then - --if TEST then - -- print('####', status.share.count, osClock() - status.clock) - --end - if status.interface and status.interface.pulse then - status.interface.pulse() - end - end - --if status.share.count >= 100000 then - -- logWarn('Count too large!') - -- break - --end - m.checkSameSimple(status, simple, obj, start, force, mode, pushQueue) - if max >= 10000 then - logWarn('Queue too large!') - break - end - end - --deallocQueue(queues, starts, forces) -end - -function m.getCallerInSameFile(status, func) - -- 搜索所有所在函数的调用者 - local funcRefs = m.status(status) - m.searchRefOfValue(funcRefs, func) - - local calls = {} - if #funcRefs.results == 0 then - return calls +---获取uri +---@param obj parser.guide.object +---@return uri +function m.getUri(obj) + if obj.uri then + return obj.uri end - for _, res in ipairs(funcRefs.results) do - local call = res.parent - if call.type == 'call' then - calls[#calls+1] = call - end + local root = m.getRoot(obj) + if root then + return root.uri end - return calls + return '' end -function m.getCallerCrossFiles(status, main) - if status.interface.link then - return status.interface.link(main.uri) - end +-- TODO +function m.findGlobals(root) + linker.compileLinks(root) + -- TODO return {} end -function m.searchRefsAsFunctionReturn(status, obj, mode) - if not status.deep then - return - end - if mode == 'def' then - return - end - if m.checkReturnMark(status, obj, true) then - return - end - status.results[#status.results+1] = obj - -- 搜索所在函数 - local currentFunc = m.getParentFunction(obj) - local rtn = obj.parent - if rtn.type ~= 'return' then - return - end - -- 看看他是第几个返回值 - local index - for i = 1, #rtn do - if obj == rtn[i] then - index = i - break - end - end - if not index then - return - end - local calls - if currentFunc.type == 'main' then - calls = m.getCallerCrossFiles(status, currentFunc) - else - calls = m.getCallerInSameFile(status, currentFunc) - end - -- 搜索调用者的返回值 - if #calls == 0 then - return - end - local selects = {} - for i = 1, #calls do - local parent = calls[i].parent - if parent.type == 'select' and parent.index == index then - selects[#selects+1] = parent.parent - end - local extParent = calls[i].extParent - if extParent then - for j = 1, #extParent do - local ext = extParent[j] - if ext.type == 'select' and ext.index == index then - selects[#selects+1] = ext.parent - end - end - end - end - -- 搜索调用者的引用 - for i = 1, #selects do - m.searchRefs(status, selects[i], 'ref') - end -end - -function m.searchRefsAsFunctionSet(status, obj, mode) - local parent = obj.parent - if not parent then - return - end - if parent.type == 'local' - or parent.type == 'setlocal' - or parent.type == 'setglobal' - or parent.type == 'setfield' - or parent.type == 'setmethod' - or parent.type == 'tablefield' then - m.searchRefs(status, parent, mode) - elseif parent.type == 'setindex' - or parent.type == 'tableindex' then - if parent.index == obj then - m.searchRefs(status, parent, mode) - end - end -end - -function m.searchRefsAsFunction(status, obj, mode) - if obj.type ~= 'function' - and obj.type ~= 'table' then - return - end - m.searchRefsAsFunctionSet(status, obj, mode) - -- 检查自己作为返回函数时的引用 - m.searchRefsAsFunctionReturn(status, obj, mode) -end - -function m.cleanResults(results) - local mark = {} - for i = #results, 1, -1 do - local res = results[i] - if res.tag == 'self' - or mark[res] then - results[i] = results[#results] - results[#results] = nil - else - mark[res] = true - end - end -end - ---function m.getRefCache(status, obj, mode) --- local cache = status.interface.cache and status.interface.cache() --- if not cache then --- return --- end --- if m.isGlobal(obj) then --- obj = m.getKeyName(obj) --- end --- if not cache[mode] then --- cache[mode] = {} --- end --- local sourceCache = cache[mode][obj] --- if sourceCache then --- return sourceCache --- end --- sourceCache = {} --- cache[mode][obj] = sourceCache --- return nil, function (results) --- for i = 1, #results do --- sourceCache[i] = results[i] --- end --- end ---end - -function m.getRefCache(status, obj, mode) - local isDeep = status.deep - if mode == 'infer' then - if not isDeep then - return nil, nil - end - end - local globalCache = status.interface.cache and status.interface.cache() or {} - if m.isGlobal(obj) then - obj = m.getKeyName(obj) - end - if not obj then - return {} - end - if not globalCache[mode] then - globalCache[mode] = {} - end - local sourceCache = globalCache[mode][obj] - if sourceCache then - return sourceCache - end - if not status.share.cacheLock[mode] then - status.share.cacheLock[mode] = {} - end - if status.share.cacheLock[mode][obj] then - return {} - end - status.share.cacheLock[mode][obj] = {} - return nil, function (results) - sourceCache = {} - for i = 1, #results do - sourceCache[i] = results[i] - end - globalCache[mode][obj] = sourceCache - if not isDeep then - return - end - if mode == 'ref' - or mode == 'def' then - for i = 1, #results do - local res = results[i] - if not globalCache[mode][res] then - globalCache[mode][res] = sourceCache - end - end - end - end +-- TODO +function m.isGlobal(source) + return false end +---搜索对象的引用 +---@param status guide.status +---@param obj parser.guide.object +---@param mode guide.searchmode function m.searchRefs(status, obj, mode) - if not obj then - return - end - local cache, makeCache = m.getRefCache(status, obj, mode) - if cache then - for i = 1, #cache do - status.results[#status.results+1] = cache[i] - end - return - end - - -- 检查单步引用 - tracy.ZoneBeginN('searchRefs getStepRef') - local res = m.getStepRef(status, obj, mode) - if res then - for i = 1, #res do - status.results[#status.results+1] = res[i] - end - end - tracy.ZoneEnd() - -- 检查simple - tracy.ZoneBeginN('searchRefs searchSameFields') - if m.checkStatusDepth(status) then - local simple = m.getSimple(obj) - if simple then - m.searchSameFields(status, simple, mode) - end - end - tracy.ZoneEnd() - - m.cleanResults(status.results) - - if makeCache then - makeCache(status.results) - end -end + local root = guide.getRoot(obj) + linker.compileLinks(root) -function m.searchRefOfValue(status, obj) - local var = obj.parent - if var.type == 'local' - or var.type == 'set' then - return m.searchRefs(status, var, 'ref') - end -end - -function m.allocInfer(o) - if type(o.type) == 'table' then - local infers = {} - for i = 1, #o.type do - infers[i] = { - type = o.type[i], - value = o.value, - source = o.source, - level = o.level - } + local links = linker.getLinkersBySource(obj) + if links then + for _, link in ipairs(links) do + m.pushResult(status, mode, link.source) end - return infers - else - return { - [1] = o, - } end end -function m.mergeTypes(types) - local hasAny = types['any'] - - types['any'] = nil - - if not next(types) then - return 'any' - end - -- 同时包含 number 与 integer 时,去掉 integer - if types['number'] and types['integer'] then - types['integer'] = nil - end - - local results = {} - for tp in pairs(types) do - results[#results+1] = tp - end - -- 只有显性的 nil 与 any 时,取 any - if #results == 1 then - if results[1] == 'nil' and hasAny then - return 'any' - else - return results[1] - end - end - - tableSort(results, function (a, b) - local sa = TypeSort[a] or 100 - local sb = TypeSort[b] or 100 - if sa == sb then - return a < b - else - return sa < sb - end - end) +---@class guide.status +---搜索结果 +---@field results parser.guide.object[] - return tableConcat(results, '|') -end - -function m.getClassExtends(class) - if class.type == 'doc.class.name' then - class = class.parent - end - if not class.extends then - return nil - end - local names = {} - for _, ext in ipairs(class.extends) do - names[#names+1] = ext[1] - end - return names -end - -function m.viewInferType(infers) - if not infers then - return 'any' - end - local types = {} - local hasDoc - local hasDocTable - for i = 1, #infers do - local infer = infers[i] - local src = infer.source - if src.type == 'doc.class' - or src.type == 'doc.class.name' - or src.type == 'doc.type.name' - or src.type == 'doc.type.array' - or src.type == 'doc.type.table' then - if infer.type ~= 'any' then - hasDoc = true - end - if src.type == 'doc.type.array' - or src.type == 'doc.type.table' then - hasDocTable = true - end - end - end - if hasDoc then - for i = 1, #infers do - local infer = infers[i] - local src = infer.source - if src.type == 'doc.class' - or src.type == 'doc.class.name' - or src.type == 'doc.type.name' - or src.type == 'doc.type.array' - or src.type == 'doc.type.table' - or src.type == 'doc.type.enum' - or src.type == 'doc.resume' then - local tp = infer.type or 'any' - if hasDocTable and tp == 'table' then - goto CONTINUE - end - if types[tp] == nil then - types[tp] = true - end - end - if src.type == 'doc.class' - or src.type == 'doc.class.name' then - local extends = m.getClassExtends(src) - if extends then - for _, tp in ipairs(extends) do - types[tp] = false - end - end - end - ::CONTINUE:: - end - for k, v in pairs(types) do - if not v then - types[k] = nil - end - end - else - for i = 1, #infers do - local infer = infers[i] - if infer.source.typeGeneric then - goto CONTINUE - end - local tp = infer.type or 'any' - types[tp] = true - ::CONTINUE:: - end - end - return m.mergeTypes(types) -end - -function m.checkTrue(status, source) - local newStatus = m.status(status) - m.searchInfer(newStatus, source) - -- 当前认为的结果 - local current - for _, infer in ipairs(newStatus.results) do - -- 新的结果 - local new - if infer.type == 'nil' then - new = false - elseif infer.type == 'boolean' then - if infer.value == true then - new = true - elseif infer.value == false then - new = false - end - end - if new ~= nil then - if current == nil then - current = new - else - -- 如果2个结果完全相反,则返回 nil 表示不确定 - if new ~= current then - return nil - end - end - end - end - return current -end - ---- 获取特定类型的字面量值 -function m.getInferLiteral(status, source, type) - local newStatus = m.status(status) - m.searchInfer(newStatus, source) - for _, infer in ipairs(newStatus.results) do - if infer.value ~= nil then - if type == nil or infer.type == type then - return infer.value - end - end - end - return nil -end - ---- 是否包含某种类型 -function m.hasType(status, source, type) - m.searchInfer(status, source) - for _, infer in ipairs(status.results) do - if infer.type == type then - return true - end - end - return false -end - -function m.isSameValue(status, a, b) - local statusA = m.status(status) - m.searchInfer(statusA, a) - local statusB = m.status(status) - m.searchInfer(statusB, b) - local infers = {} - for _, infer in ipairs(statusA.results) do - local literal = infer.value - if literal then - infers[literal] = false - end - end - for _, infer in ipairs(statusB.results) do - local literal = infer.value - if literal then - if infers[literal] == nil then - return false - end - infers[literal] = true - end - end - for k, v in pairs(infers) do - if v == false then - return false - end - end - return true -end - -function m.inferCheckLiteralTableWithDocVararg(status, source) - if #source ~= 1 then - return - end - local vararg = source[1] - if vararg.type ~= 'varargs' then - return - end - local results = m.getVarargDocType(status, source) - status.results[#status.results+1] = { - type = m.viewInferType(results) .. '[]', - source = source, - level = 100, +---创建搜索状态 +---@param parentStatus guide.status +---@param interface table +---@param deep integer +---@return guide.status +function m.status(parentStatus, interface, deep) + local status = { + share = parentStatus and parentStatus.share or { + count = 0, + cacheLock = {}, + }, + depth = parentStatus and (parentStatus.depth + 1) or 0, + searchDeep= parentStatus and parentStatus.searchDeep or deep or -999, + interface = parentStatus and parentStatus.interface or {}, + deep = parentStatus and parentStatus.deep, + clock = parentStatus and parentStatus.clock or osClock(), + results = {}, } - return true -end - -function m.inferCheckLiteral(status, source) - if source.type == 'string' then - status.results = m.allocInfer { - type = 'string', - value = source[1], - source = source, - level = 100, - } - return true - elseif source.type == 'nil' then - status.results = m.allocInfer { - type = 'nil', - value = NIL, - source = source, - level = 100, - } - return true - elseif source.type == 'boolean' then - status.results = m.allocInfer { - type = 'boolean', - value = source[1], - source = source, - level = 100, - } - return true - elseif source.type == 'number' then - if mathType(source[1]) == 'integer' then - status.results = m.allocInfer { - type = 'integer', - value = source[1], - source = source, - level = 100, - } - return true - else - status.results = m.allocInfer { - type = 'number', - value = source[1], - source = source, - level = 100, - } - return true - end - elseif source.type == 'integer' then - status.results = m.allocInfer { - type = 'integer', - source = source, - level = 100, - } - return true - elseif source.type == 'table' then - if m.inferCheckLiteralTableWithDocVararg(status, source) then - return true - end - status.results = m.allocInfer { - type = 'table', - source = source, - level = 100, - } - return true - elseif source.type == 'function' then - status.results = m.allocInfer { - type = 'function', - source = source, - level = 100, - } - return true - elseif source.type == '...' then - status.results = m.allocInfer { - type = '...', - source = source, - level = 100, - } - return true - end -end - -local function getDocAliasExtends(status, typeUnit) - if not status.interface.docType then - return nil - end - if typeUnit.type ~= 'doc.type.name' then - return nil - end - for _, doc in ipairs(status.interface.docType(typeUnit[1])) do - if doc.type == 'doc.alias.name' then - return doc.parent.extends - end - end - return nil -end - -function m.getDocTypeUnitName(status, unit) - local typeName - if unit.type == 'doc.type.name' then - typeName = unit[1] - elseif unit.type == 'doc.type.function' then - typeName = 'function' - elseif unit.type == 'doc.type.array' then - typeName = m.getDocTypeUnitName(status, unit.node) .. '[]' - elseif unit.type == 'doc.type.table' then - typeName = ('%s<%s, %s>'):format( - m.getDocTypeUnitName(status, unit.node), - m.viewInferType(m.getDocTypeNames(status, unit.key)), - m.viewInferType(m.getDocTypeNames(status, unit.value)) - ) - end - if unit.typeGeneric then - typeName = ('<%s>'):format(typeName) - end - return typeName -end - -function m.getDocTypeNames(status, doc) - local results = {} - if not doc then - return results - end - for _, unit in ipairs(doc.types) do - local alias = getDocAliasExtends(status, unit) - if alias then - local aliasResults = m.getDocTypeNames(status, alias) - for _, res in ipairs(aliasResults) do - results[#results+1] = res - end - else - local typeName = m.getDocTypeUnitName(status, unit) - results[#results+1] = { - type = typeName, - source = unit, - level = 100, - } - end - end - for _, enum in ipairs(doc.enums) do - results[#results+1] = { - type = enum[1], - source = enum, - level = 100, - } - end - for _, resume in ipairs(doc.resumes) do - if not resume.additional then - results[#results+1] = { - type = resume[1], - source = resume, - level = 100, - } - end - end - return results -end - -function m.inferCheckDoc(status, source) - if source.type == 'doc.class.name' then - status.results[#status.results+1] = { - type = source[1], - source = source, - level = 100, - } - return true - end - if source.type == 'doc.class' then - status.results[#status.results+1] = { - type = source.class[1], - source = source, - level = 100, - } - return true - end - if source.type == 'doc.type' then - local results = m.getDocTypeNames(status, source) - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - return true - end - if source.type == 'doc.type.function' - or source.type == 'doc.type.table' - or source.type == 'doc.type.array' then - local typeName = m.getDocTypeUnitName(status, source) - status.results[#status.results+1] = { - type = typeName, - source = source, - level = 100, - } - return true - end - if source.type == 'doc.field' then - local results = m.getDocTypeNames(status, source.extends) - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - return true - end - if source.type == 'doc.alias.name' then - local results = m.getDocTypeNames(status, m.getDocState(source).extends) - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - return true - end -end - -function m.getVarargDocType(status, source) - local func = m.getParentFunction(source) - if not func then - return - end - if not func.args then - return - end - for _, arg in ipairs(func.args) do - if arg.type == '...' then - if arg.bindDocs then - for _, doc in ipairs(arg.bindDocs) do - if doc.type == 'doc.vararg' then - return m.getDocTypeNames(status, doc.vararg) - end - end - end - end - end -end - -function m.inferCheckUpDocOfVararg(status, source) - if not source.vararg then - return - end - local results = m.getVarargDocType(status, source) - if not results then - return - end - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - return true -end - -function m.inferCheckUpDoc(status, source) - if m.inferCheckUpDocOfVararg(status, source) then - return true - end - local parent = source.parent - if parent then - if parent.type == 'local' - or parent.type == 'setlocal' - or parent.type == 'setglobal' then - source = parent - end - if parent.type == 'setfield' - or parent.type == 'tablefield' then - if parent.field == source - or parent.value == source then - source = parent - end - end - if parent.type == 'setmethod' then - if parent.method == source - or parent.value == source then - source = parent - end - end - if parent.type == 'setindex' - or parent.type == 'tableindex' then - if parent.index == source - or parent.value == source then - source = parent - end - end - end - local binds = source.bindDocs - if not binds then - return - end - status.results = {} - for i = #binds, 1, -1 do - local doc = binds[i] - if doc.type == 'doc.class' then - status.results[#status.results+1] = { - type = doc.class[1], - source = doc, - level = 100, - } - -- ---@class Class - -- local x = { field = 1 } - -- 这种情况下,将字面量表接受为Class的定义 - if source.value and source.value.type == 'table' then - status.results[#status.results+1] = { - type = source.value.type, - source = source.value, - level = 100, - } - end - return true - elseif doc.type == 'doc.type' then - local results = m.getDocTypeNames(status, doc) - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - return true - elseif doc.type == 'doc.param' then - -- function (x) 的情况 - if source.type == 'local' - and m.getKeyName(source) == doc.param[1] then - if source.parent.type == 'funcargs' - or source.parent.type == 'in' - or source.parent.type == 'loop' then - local results = m.getDocTypeNames(status, doc.extends) - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - return true - end - end - end - end -end - -function m.inferCheckUnary(status, source) - if source.type ~= 'unary' then - return - end - local op = source.op - if op.type == 'not' then - local checkTrue = m.checkTrue(status, source[1]) - local value = nil - if checkTrue == true then - value = false - elseif checkTrue == false then - value = true - end - status.results = m.allocInfer { - type = 'boolean', - value = value, - source = source, - level = 100, - } - return true - elseif op.type == '#' then - status.results = m.allocInfer { - type = 'integer', - source = source, - level = 100, - } - return true - elseif op.type == '~' then - local l = m.getInferLiteral(status, source[1], 'integer') - status.results = m.allocInfer { - type = 'integer', - value = l and ~l or nil, - source = source, - level = 100, - } - return true - elseif op.type == '-' then - local v = m.getInferLiteral(status, source[1], 'integer') - if v then - status.results = m.allocInfer { - type = 'integer', - value = - v, - source = source, - level = 100, - } - return true - end - v = m.getInferLiteral(status, source[1], 'number') - status.results = m.allocInfer { - type = 'number', - value = v and -v or nil, - source = source, - level = 100, - } - return true - end -end - -local function mathCheck(status, a, b) - local v1 = m.getInferLiteral(status, a, 'integer') - or m.getInferLiteral(status, a, 'number') - local v2 = m.getInferLiteral(status, b, 'integer') - or m.getInferLiteral(status, a, 'number') - v1 = tonumber(v1) - v2 = tonumber(v2) - local int = m.hasType(status, a, 'integer') - and m.hasType(status, b, 'integer') - and not m.hasType(status, a, 'number') - and not m.hasType(status, b, 'number') - return int and 'integer' or 'number', v1, v2 -end - -function m.inferCheckBinary(status, source) - if source.type ~= 'binary' then - return - end - local op = source.op - if op.type == 'and' then - local isTrue = m.checkTrue(status, source[1]) - if isTrue == true then - m.searchInfer(status, source[2]) - return true - elseif isTrue == false then - m.searchInfer(status, source[1]) - return true - else - m.searchInfer(status, source[1]) - m.searchInfer(status, source[2]) - return true - end - elseif op.type == 'or' then - local isTrue = m.checkTrue(status, source[1]) - if isTrue == true then - m.searchInfer(status, source[1]) - return true - elseif isTrue == false then - m.searchInfer(status, source[2]) - return true - else - m.searchInfer(status, source[1]) - m.searchInfer(status, source[2]) - return true - end - elseif op.type == '==' then - local value = m.isSameValue(status, source[1], source[2]) - if value ~= nil then - status.results = m.allocInfer { - type = 'boolean', - value = value, - source = source, - level = 100, - } - return true - end - --local isSame = m.isSameDef(status, source[1], source[2]) - --if isSame == true then - -- value = true - --else - -- value = nil - --end - status.results = m.allocInfer { - type = 'boolean', - value = value, - source = source, - level = 100, - } - return true - elseif op.type == '~=' then - local value = m.isSameValue(status, source[1], source[2]) - if value ~= nil then - status.results = m.allocInfer { - type = 'boolean', - value = not value, - source = source, - level = 100, - } - return true - end - --local isSame = m.isSameDef(status, source[1], source[2]) - --if isSame == true then - -- value = false - --else - -- value = nil - --end - status.results = m.allocInfer { - type = 'boolean', - value = value, - source = source, - level = 100, - } - return true - elseif op.type == '<=' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - or m.getInferLiteral(status, source[1], 'number') - local v2 = m.getInferLiteral(status, source[2], 'integer') - or m.getInferLiteral(status, source[2], 'number') - v1 = tonumber(v1) - v2 = tonumber(v2) - local v - if v1 and v2 then - v = v1 <= v2 - end - status.results = m.allocInfer { - type = 'boolean', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '>=' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - or m.getInferLiteral(status, source[1], 'number') - local v2 = m.getInferLiteral(status, source[2], 'integer') - or m.getInferLiteral(status, source[2], 'number') - v1 = tonumber(v1) - v2 = tonumber(v2) - local v - if v1 and v2 then - v = v1 >= v2 - end - status.results = m.allocInfer { - type = 'boolean', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '<' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - or m.getInferLiteral(status, source[1], 'number') - local v2 = m.getInferLiteral(status, source[2], 'integer') - or m.getInferLiteral(status, source[2], 'number') - v1 = tonumber(v1) - v2 = tonumber(v2) - local v - if v1 and v2 then - v = v1 < v2 - end - status.results = m.allocInfer { - type = 'boolean', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '>' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - or m.getInferLiteral(status, source[1], 'number') - local v2 = m.getInferLiteral(status, source[2], 'integer') - or m.getInferLiteral(status, source[2], 'number') - v1 = tonumber(v1) - v2 = tonumber(v2) - local v - if v1 and v2 then - v = v1 > v2 - end - status.results = m.allocInfer { - type = 'boolean', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '|' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - local v2 = m.getInferLiteral(status, source[2], 'integer') - v1 = tointeger(v1) - v2 = tointeger(v2) - local v - if v1 and v2 then - v = v1 | v2 - end - status.results = m.allocInfer { - type = 'integer', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '~' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - local v2 = m.getInferLiteral(status, source[2], 'integer') - v1 = tointeger(v1) - v2 = tointeger(v2) - local v - if v1 and v2 then - v = v1 ~ v2 - end - status.results = m.allocInfer { - type = 'integer', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '&' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - local v2 = m.getInferLiteral(status, source[2], 'integer') - v1 = tointeger(v1) - v2 = tointeger(v2) - local v - if v1 and v2 then - v = v1 & v2 - end - status.results = m.allocInfer { - type = 'integer', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '<<' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - local v2 = m.getInferLiteral(status, source[2], 'integer') - v1 = tointeger(v1) - v2 = tointeger(v2) - local v - if v1 and v2 then - v = v1 << v2 - end - status.results = m.allocInfer { - type = 'integer', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '>>' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - local v2 = m.getInferLiteral(status, source[2], 'integer') - v1 = tointeger(v1) - v2 = tointeger(v2) - local v - if v1 and v2 then - v = v1 >> v2 - end - status.results = m.allocInfer { - type = 'integer', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '..' then - local v1 = m.getInferLiteral(status, source[1], 'string') - local v2 = m.getInferLiteral(status, source[2], 'string') - v1 = type(v1) == 'string' and v1 or nil - v2 = type(v2) == 'string' and v2 or nil - local v - if v1 and v2 then - v = v1 .. v2 - end - status.results = m.allocInfer { - type = 'string', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '^' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - or m.getInferLiteral(status, source[1], 'number') - local v2 = m.getInferLiteral(status, source[2], 'integer') - or m.getInferLiteral(status, source[2], 'number') - v1 = tonumber(v1) - v2 = tonumber(v2) - local v - if v1 and v2 then - v = v1 ^ v2 - end - status.results = m.allocInfer { - type = 'number', - value = v, - source = source, - level = 100, - } - return true - elseif op.type == '/' then - local v1 = m.getInferLiteral(status, source[1], 'integer') - or m.getInferLiteral(status, source[1], 'number') - local v2 = m.getInferLiteral(status, source[2], 'integer') - or m.getInferLiteral(status, source[2], 'number') - v1 = tonumber(v1) - v2 = tonumber(v2) - local v - if v1 and v2 and v2 ~= 0 then - v = v1 / v2 - end - status.results = m.allocInfer { - type = 'number', - value = v, - source = source, - level = 100, - } - return true - -- 其他数学运算根据2侧的值决定,当2侧的值均为整数时返回整数 - elseif op.type == '+' then - local int, v1, v2 = mathCheck(status, source[1], source[2]) - status.results = m.allocInfer{ - type = int, - value = (v1 and v2) and (v1 + v2) or nil, - source = source, - level = 100, - } - return true - elseif op.type == '-' then - local int, v1, v2 = mathCheck(status, source[1], source[2]) - status.results = m.allocInfer{ - type = int, - value = (v1 and v2) and (v1 - v2) or nil, - source = source, - level = 100, - } - return true - elseif op.type == '*' then - local int, v1, v2 = mathCheck(status, source[1], source[2]) - status.results = m.allocInfer { - type = int, - value = (v1 and v2) and (v1 * v2) or nil, - source = source, - level = 100, - } - return true - elseif op.type == '%' then - local int, v1, v2 = mathCheck(status, source[1], source[2]) - status.results = m.allocInfer { - type = int, - value = (v1 and v2 and v2 ~= 0) and (v1 % v2) or nil, - source = source, - level = 100, - } - return true - elseif op.type == '//' then - local int, v1, v2 = mathCheck(status, source[1], source[2]) - status.results = m.allocInfer { - type = int, - value = (v1 and v2 and v2 ~= 0) and (v1 // v2) or nil, - source = source, - level = 100, - } - return true - end -end - -function m.inferByDef(status, obj) - if not status.share.inferedDef then - status.share.inferedDef = {} - end - if status.share.inferedDef[obj] then - return - end - status.share.inferedDef[obj] = true - local mark = {} - local newStatus = m.status(status, status.interface) - tracy.ZoneBeginN('inferByDef searchRefs') - m.searchRefs(newStatus, obj, 'def') - tracy.ZoneEnd() - for _, src in ipairs(newStatus.results) do - local inferStatus = m.status(newStatus) - m.searchInfer(inferStatus, src) - if #inferStatus.results == 0 then - status.results[#status.results+1] = { - type = 'any', - source = src, - level = 0, - } - else - for _, infer in ipairs(inferStatus.results) do - if not mark[infer.source] then - mark[infer.source] = true - status.results[#status.results+1] = infer - end - end - end - end -end - -local function inferBySetOfLocal(status, source) - if status.share[source] then - return - end - status.share[source] = true - local newStatus = m.status(status) - if source.value then - m.searchInfer(newStatus, source.value) - end - if source.ref then - for _, ref in ipairs(source.ref) do - if ref.type == 'setlocal' then - break - end - m.searchInfer(newStatus, ref) - end - for _, infer in ipairs(newStatus.results) do - status.results[#status.results+1] = infer - end - end -end - -function m.inferByLocalRef(status, source) - if #status.results ~= 0 then - return - end - if source.type == 'local' then - inferBySetOfLocal(status, source) - elseif source.type == 'setlocal' - or source.type == 'getlocal' then - inferBySetOfLocal(status, source.node) - end -end - -function m.inferByCall(status, source) - if not source.parent then - return - end - if source.parent.type ~= 'call' then - return - end - if source.parent.node == source then - status.results[#status.results+1] = { - type = 'function', - source = source, - level = 10, - } - return - end -end - -function m.inferByGetTable(status, source) - if source.type == 'field' - or source.type == 'method' then - source = source.parent - end - local next = source.next - if not next then - return - end - if next.type == 'getfield' - or next.type == 'getindex' - or next.type == 'setmethod' - or next.type == 'setfield' - or next.type == 'setindex' then - status.results[#status.results+1] = { - type = 'table', - source = source, - level = 10, - } - elseif next.type == 'getmethod' then - status.results[#status.results+1] = { - type = 'table', - source = source, - level = 5, - } - status.results[#status.results+1] = { - type = 'string', - source = source, - level = 5, - } - end -end - -function m.inferByUnary(status, source) - local parent = source.parent - if not parent or parent.type ~= 'unary' then - return - end - local op = parent.op - if op.type == '#' then - status.results[#status.results+1] = { - type = 'string', - source = source, - level = 5, - } - status.results[#status.results+1] = { - type = 'table', - source = source, - level = 5, - } - elseif op.type == '~' then - status.results[#status.results+1] = { - type = 'integer', - source = source, - level = 10, - } - elseif op.type == '-' then - status.results[#status.results+1] = { - type = 'number', - source = source, - level = 10, - } - end -end - -function m.inferByBinary(status, source) - local parent = source.parent - if not parent or parent.type ~= 'binary' then - return - end - local op = parent.op - if op.type == '<=' - or op.type == '>=' - or op.type == '<' - or op.type == '>' - or op.type == '^' - or op.type == '/' - or op.type == '+' - or op.type == '-' - or op.type == '*' - or op.type == '%' then - status.results[#status.results+1] = { - type = 'number', - source = source, - level = 10, - } - elseif op.type == '|' - or op.type == '~' - or op.type == '&' - or op.type == '<<' - or op.type == '>>' - -- 整数的可能性比较高 - or op.type == '//' then - status.results[#status.results+1] = { - type = 'integer', - source = source, - level = 10, - } - elseif op.type == '..' then - status.results[#status.results+1] = { - type = 'string', - source = source, - level = 10, - } - end -end - -local function mergeFunctionReturnsByDoc(status, source, index, call) - if not source or source.type ~= 'function' then - return - end - if not source.bindDocs then - return - end - local returns = {} - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.return' then - for _, rtn in ipairs(doc.returns) do - returns[#returns+1] = rtn - end - end - end - local rtn = returns[index] - if not rtn then - return - end - local results = m.getDocTypeNames(status, rtn) - if #results == 0 then - return - end - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - return true -end - -local function mergeDocTypeFunctionReturns(status, source, index) - if not source.bindDocs then - return - end - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.type' then - for _, typeUnit in ipairs(doc.types) do - if typeUnit.type == 'doc.type.function' then - local rtn = typeUnit.returns[index] - if rtn then - local results = m.getDocTypeNames(status, rtn) - for _, res in ipairs(results) do - status.results[#status.results+1] = res - end - end - end - end - end - end -end - -local function mergeFunctionReturns(status, source, index, call) - local returns = source.returns - if not returns then - return - end - for i = 1, #returns do - local rtn = returns[i] - if rtn[index] then - if rtn[index].type == 'call' then - if not m.checkReturnMark(status, rtn[index], true) then - m.inferByCallReturnAndIndex(status, rtn[index], index) - end - else - local newStatus = m.status(status) - m.searchInfer(newStatus, rtn[index]) - if #newStatus.results == 0 then - status.results[#status.results+1] = { - type = 'any', - source = rtn[index], - level = 0, - } - else - for _, infer in ipairs(newStatus.results) do - status.results[#status.results+1] = infer - end - end - end - end - end -end - -function m.inferByCallReturnAndIndex(status, call, index) - local node = call.node - local newStatus = m.status(status, status.interface) - m.searchRefs(newStatus, node, 'def') - local hasDocReturn - for _, src in ipairs(newStatus.results) do - if mergeDocTypeFunctionReturns(status, src, index) then - hasDocReturn = true - elseif mergeFunctionReturnsByDoc(status, src.value, index, call) then - hasDocReturn = true - end - end - if not hasDocReturn then - for _, src in ipairs(newStatus.results) do - if src.value and src.value.type == 'function' then - if not m.checkReturnMark(status, src.value, true) then - mergeFunctionReturns(status, src.value, index, call) - end - end - end - end -end - -function m.inferByCallReturn(status, source) - if source.type == 'call' then - m.inferByCallReturnAndIndex(status, source, 1) - return - end - if source.type ~= 'select' then - if source.value and source.value.type == 'select' then - source = source.value - else - return - end - end - if not source.vararg or source.vararg.type ~= 'call' then - return - end - m.inferByCallReturnAndIndex(status, source.vararg, source.index) -end - -function m.inferByPCallReturn(status, source) - if source.type ~= 'select' then - if source.value and source.value.type == 'select' then - source = source.value - else - return - end - end - local call = source.vararg - if not call or call.type ~= 'call' then - return - end - if not call.args then - return - end - local node = call.node - local specialName = node.special - local func, index - if specialName == 'pcall' then - func = call.args[1] - index = source.index - 1 - elseif specialName == 'xpcall' then - func = call.args[1] - index = source.index - 2 - else - return - end - local newStatus = m.status(status, status.interface) - m.searchRefs(newStatus, func, 'def') - for _, src in ipairs(newStatus.results) do - if src.value and src.value.type == 'function' then - mergeFunctionReturns(status, src.value, index) - end - end -end - -function m.cleanInfers(infers, obj) - -- kick lower level infers - local level = 0 - if obj.type ~= 'select' then - for i = 1, #infers do - local infer = infers[i] - if infer.level > level then - level = infer.level - end - end - end - -- merge infers - local mark = {} - for i = #infers, 1, -1 do - local infer = infers[i] - if infer.level < level then - infers[i] = infers[#infers] - infers[#infers] = nil - goto CONTINUE - end - local key = ('%p'):format(infer.type, infer.source) - if mark[key] then - infers[i] = infers[#infers] - infers[#infers] = nil - else - mark[key] = true - end - ::CONTINUE:: - end - -- kick doc.generic - if #infers > 1 then - for i = #infers, 1, -1 do - local infer = infers[i] - if infer.source.typeGeneric then - infers[i] = infers[#infers] - infers[#infers] = nil - end - end - end -end - -function m.searchInfer(status, obj) - while obj.type == 'paren' do - obj = obj.exp - if not obj then - return - end - end - while true do - local value = m.getObjectValue(obj) - if not value or value == obj then - break - end - obj = value - end - if not obj then - return - end - - local cache, makeCache = m.getRefCache(status, obj, 'infer') - if cache then - for i = 1, #cache do - status.results[#status.results+1] = cache[i] - end - return - end - - if DEVELOP then - status.share.clock = status.share.clock or osClock() - end - - if not status.share.lockInfer then - status.share.lockInfer = {} - end - if status.share.lockInfer[obj] then - return - end - status.share.lockInfer[obj] = true - - local checked = m.inferCheckDoc(status, obj) - or m.inferCheckUpDoc(status, obj) - or m.inferCheckLiteral(status, obj) - or m.inferCheckUnary(status, obj) - or m.inferCheckBinary(status, obj) - if checked then - m.cleanInfers(status.results, obj) - if makeCache then - makeCache(status.results) + if interface then + for k, v in pairs(interface) do + status.interface[k] = v end - return - end - - m.inferByLocalRef(status, obj) - if status.deep then - tracy.ZoneBeginN('inferByDef') - m.inferByDef(status, obj) - tracy.ZoneEnd() - end - --m.inferByCall(status, obj) - m.inferByGetTable(status, obj) - m.inferByUnary(status, obj) - m.inferByBinary(status, obj) - m.cleanInfers(status.results, obj) - if makeCache then - makeCache(status.results) end + status.deep = status.depth <= status.searchDeep + return status end ---- 请求对象的引用,包括 `a.b.c` 形式 ---- 与 `return function` 形式。 ---- 不穿透 `setmetatable` ,考虑由 ---- 业务层进行反向 def 搜索。 +--- 请求对象的引用 +---@param obj parser.guide.object +---@param interface table +---@param deep integer +---@return parser.guide.object[] +---@return integer function m.requestReference(obj, interface, deep) local status = m.status(nil, interface, deep) -- 根据 field 搜索引用 m.searchRefs(status, obj, 'ref') - m.searchRefsAsFunction(status, obj, 'ref') - - if m.debugMode then - --print('count:', status.share.count) - end - - return status.results, status.share.count -end - ---- 请求对象的定义,包括 `a.b.c` 形式 ---- 与 `return function` 形式。 ---- 穿透 `setmetatable` 。 -function m.requestDefinition(obj, interface, deep) - local status = m.status(nil, interface, deep) - -- 根据 field 搜索定义 - m.searchRefs(status, obj, 'def') - - return status.results, status.share.count -end - ---- 请求对象的字段 ----@param filterKey nil|string|table -function m.requestFields(obj, interface, deep, filterKey) - local status = m.status(nil, interface, deep) - - m.searchFields(status, obj, filterKey) - - return status.results, status.share.count -end - ---- 请求对象的定义字段 ----@param filterKey nil|string|table -function m.requestDefFields(obj, interface, deep, filterKey) - local status = m.status(nil, interface, deep) - - m.searchDefFields(status, obj, filterKey) - - return status.results, status.share.count -end - ---- 请求对象的类型推测 -function m.requestInfer(obj, interface, deep) - local status = m.status(nil, interface, deep) - m.searchInfer(status, obj) - - return status.results, status.share.count -end - -function m.debugView(obj) - return require 'files'.position(m.getUri(obj), obj.start), m.getUri(obj) + return status.results, 0 end return m diff --git a/script/core/guide2.lua b/script/core/guide2.lua new file mode 100644 index 00000000..64192297 --- /dev/null +++ b/script/core/guide2.lua @@ -0,0 +1,4837 @@ +local util = require 'utility' +local error = error +local type = type +local next = next +local tostring = tostring +local print = print +local ipairs = ipairs +local tableInsert = table.insert +local tableUnpack = table.unpack +local tableRemove = table.remove +local tableMove = table.move +local tableSort = table.sort +local tableConcat = table.concat +local mathType = math.type +local pairs = pairs +local setmetatable = setmetatable +local assert = assert +local select = select +local osClock = os.clock +local tonumber = tonumber +local tointeger = math.tointeger +local DEVELOP = _G.DEVELOP +local log = log +local _G = _G + +local function logWarn(...) + log.warn(...) +end + +local m = {} + +m.ANY = {"<ANY>"} + +local blockTypes = { + ['while'] = true, + ['in'] = true, + ['loop'] = true, + ['repeat'] = true, + ['do'] = true, + ['function'] = true, + ['ifblock'] = true, + ['elseblock'] = true, + ['elseifblock'] = true, + ['main'] = true, +} + +local breakBlockTypes = { + ['while'] = true, + ['in'] = true, + ['loop'] = true, + ['repeat'] = true, +} + +m.childMap = { + ['main'] = {'#', 'docs'}, + ['repeat'] = {'#', 'filter'}, + ['while'] = {'filter', '#'}, + ['in'] = {'keys', '#'}, + ['loop'] = {'loc', 'max', 'step', '#'}, + ['if'] = {'#'}, + ['ifblock'] = {'filter', '#'}, + ['elseifblock'] = {'filter', '#'}, + ['elseblock'] = {'#'}, + ['setfield'] = {'node', 'field', 'value'}, + ['setglobal'] = {'value'}, + ['local'] = {'attrs', 'value'}, + ['setlocal'] = {'value'}, + ['return'] = {'#'}, + ['do'] = {'#'}, + ['select'] = {'vararg'}, + ['table'] = {'#'}, + ['tableindex'] = {'index', 'value'}, + ['tablefield'] = {'field', 'value'}, + ['function'] = {'args', '#'}, + ['funcargs'] = {'#'}, + ['setmethod'] = {'node', 'method', 'value'}, + ['getmethod'] = {'node', 'method'}, + ['setindex'] = {'node', 'index', 'value'}, + ['getindex'] = {'node', 'index'}, + ['paren'] = {'exp'}, + ['call'] = {'node', 'args'}, + ['callargs'] = {'#'}, + ['getfield'] = {'node', 'field'}, + ['list'] = {'#'}, + ['binary'] = {1, 2}, + ['unary'] = {1}, + + ['doc'] = {'#'}, + ['doc.class'] = {'class', '#extends', 'comment'}, + ['doc.type'] = {'#types', '#enums', 'name', 'comment'}, + ['doc.alias'] = {'alias', 'extends', 'comment'}, + ['doc.param'] = {'param', 'extends', 'comment'}, + ['doc.return'] = {'#returns', 'comment'}, + ['doc.field'] = {'field', 'extends', 'comment'}, + ['doc.generic'] = {'#generics', 'comment'}, + ['doc.generic.object'] = {'generic', 'extends', 'comment'}, + ['doc.vararg'] = {'vararg', 'comment'}, + ['doc.type.array'] = {'node'}, + ['doc.type.table'] = {'node', 'key', 'value', 'comment'}, + ['doc.type.function'] = {'#args', '#returns', 'comment'}, + ['doc.type.typeliteral'] = {'node'}, + ['doc.type.arg'] = {'extends'}, + ['doc.overload'] = {'overload', 'comment'}, + ['doc.see'] = {'name', 'field', 'comment'}, + ['doc.diagnostic'] = {'#names', 'comment'}, +} + +m.actionMap = { + ['main'] = {'#'}, + ['repeat'] = {'#'}, + ['while'] = {'#'}, + ['in'] = {'#'}, + ['loop'] = {'#'}, + ['if'] = {'#'}, + ['ifblock'] = {'#'}, + ['elseifblock'] = {'#'}, + ['elseblock'] = {'#'}, + ['do'] = {'#'}, + ['function'] = {'#'}, + ['funcargs'] = {'#'}, +} + +local TypeSort = { + ['boolean'] = 1, + ['string'] = 2, + ['integer'] = 3, + ['number'] = 4, + ['table'] = 5, + ['function'] = 6, + ['true'] = 101, + ['false'] = 102, + ['nil'] = 999, +} + +local NIL = setmetatable({'<nil>'}, { __tostring = function () return 'nil' end }) + +--- 是否是字面量 +---@param obj parser.guide.object +---@return boolean +function m.isLiteral(obj) + local tp = obj.type + return tp == 'nil' + or tp == 'boolean' + or tp == 'string' + or tp == 'number' + or tp == 'table' + or tp == 'function' +end + +--- 获取字面量 +---@param obj parser.guide.object +---@return any +function m.getLiteral(obj) + local tp = obj.type + if tp == 'boolean' then + return obj[1] + elseif tp == 'string' then + return obj[1] + elseif tp == 'number' then + return obj[1] + end + return nil +end + +--- 寻找父函数 +---@param obj parser.guide.object +---@return parser.guide.object +function m.getParentFunction(obj) + for _ = 1, 1000 do + obj = obj.parent + if not obj then + break + end + local tp = obj.type + if tp == 'function' or tp == 'main' then + return obj + end + end + return nil +end + +--- 寻找父的table类型 doc.type.table +---@param obj parser.guide.object +---@return parser.guide.object +function m.getParentDocTypeTable(obj) + for _ = 1, 1000 do + local parent = obj.parent + if not parent then + return nil + end + if parent.type == 'doc.type.table' then + return obj + end + obj = parent + end + error('guide.getParentDocTypeTable overstack') +end + +--- 寻找所在区块 +---@param obj parser.guide.object +---@return parser.guide.object +function m.getBlock(obj) + for _ = 1, 1000 do + if not obj then + return nil + end + local tp = obj.type + if blockTypes[tp] then + return obj + end + if obj == obj.parent then + error('obj == obj.parent?', obj.type) + end + obj = obj.parent + end + error('guide.getBlock overstack') +end + +--- 寻找所在父区块 +---@param obj parser.guide.object +---@return parser.guide.object +function m.getParentBlock(obj) + for _ = 1, 1000 do + obj = obj.parent + if not obj then + return nil + end + local tp = obj.type + if blockTypes[tp] then + return obj + end + end + error('guide.getParentBlock overstack') +end + +--- 寻找所在可break的父区块 +---@param obj parser.guide.object +---@return parser.guide.object +function m.getBreakBlock(obj) + for _ = 1, 1000 do + obj = obj.parent + if not obj then + return nil + end + local tp = obj.type + if breakBlockTypes[tp] then + return obj + end + if tp == 'function' then + return nil + end + end + error('guide.getBreakBlock overstack') +end + +--- 寻找doc的主体 +---@param obj parser.guide.object +---@return parser.guide.object +function m.getDocState(obj) + for _ = 1, 1000 do + local parent = obj.parent + if not parent then + return obj + end + if parent.type == 'doc' then + return obj + end + obj = parent + end + error('guide.getDocState overstack') +end + +--- 寻找所在父类型 +---@param obj parser.guide.object +---@return parser.guide.object +function m.getParentType(obj, want) + for _ = 1, 1000 do + obj = obj.parent + if not obj then + return nil + end + if want == obj.type then + return obj + end + end + error('guide.getParentType overstack') +end + +--- 寻找根区块 +---@param obj parser.guide.object +---@return parser.guide.object +function m.getRoot(obj) + local source = obj + if source._root then + return source._root + end + for _ = 1, 1000 do + if obj.type == 'main' then + source._root = obj + return obj + end + local parent = obj.parent + if not parent then + return nil + end + obj = parent + end + error('guide.getRoot overstack') +end + +---@param obj parser.guide.object +---@return string +function m.getUri(obj) + if obj.uri then + return obj.uri + end + local root = m.getRoot(obj) + if root then + return root.uri + end + return '' +end + +function m.getENV(source, start) + if not start then + start = 1 + end + return m.getLocal(source, '_ENV', start) + or m.getLocal(source, '@fenv', start) +end + +--- 寻找函数的不定参数,返回不定参在第几个参数上,以及该参数对象。 +--- 如果函数是主函数,则返回`0, nil`。 +---@return table +---@return integer +function m.getFunctionVarArgs(func) + if func.type == 'main' then + return 0, nil + end + if func.type ~= 'function' then + return nil, nil + end + local args = func.args + if not args then + return nil, nil + end + for i = 1, #args do + local arg = args[i] + if arg.type == '...' then + return i, arg + end + end + return nil, nil +end + +--- 获取指定区块中可见的局部变量 +---@param block table +---@param name string {comment = '变量名'} +---@param pos integer {comment = '可见位置'} +function m.getLocal(block, name, pos) + block = m.getBlock(block) + for _ = 1, 1000 do + if not block then + return nil + end + local locals = block.locals + local res + if not locals then + goto CONTINUE + end + for i = 1, #locals do + local loc = locals[i] + if loc.effect > pos then + break + end + if loc[1] == name then + if not res or res.effect < loc.effect then + res = loc + end + end + end + if res then + return res, res + end + ::CONTINUE:: + block = m.getParentBlock(block) + end + error('guide.getLocal overstack') +end + +--- 获取指定区块中所有的可见局部变量名称 +function m.getVisibleLocals(block, pos) + local result = {} + m.eachSourceContain(m.getRoot(block), pos, function (source) + local locals = source.locals + if locals then + for i = 1, #locals do + local loc = locals[i] + local name = loc[1] + if loc.effect <= pos then + result[name] = loc + end + end + end + end) + return result +end + +--- 获取指定区块中可见的标签 +---@param block table +---@param name string {comment = '标签名'} +function m.getLabel(block, name) + block = m.getBlock(block) + for _ = 1, 1000 do + if not block then + return nil + end + local labels = block.labels + if labels then + local label = labels[name] + if label then + return label + end + end + if block.type == 'function' then + return nil + end + block = m.getParentBlock(block) + end + error('guide.getLocal overstack') +end + +function m.getStartFinish(source) + local start = source.start + local finish = source.finish + if not start then + local first = source[1] + if not first then + return nil, nil + end + local last = source[#source] + start = first.start + finish = last.finish + end + return start, finish +end + +function m.getRange(source) + local start = source.vstart or source.start + local finish = source.range or source.finish + if not start then + local first = source[1] + if not first then + return nil, nil + end + local last = source[#source] + start = first.vstart or first.start + finish = last.range or last.finish + end + return start, finish +end + +--- 判断source是否包含offset +function m.isContain(source, offset) + local start, finish = m.getStartFinish(source) + if not start then + return false + end + return start <= offset and finish >= offset +end + +--- 判断offset在source的影响范围内 +--- +--- 主要针对赋值等语句时,key包含value +function m.isInRange(source, offset) + local start, finish = m.getRange(source) + if not start then + return false + end + return start <= offset and finish >= offset +end + +function m.isBetween(source, tStart, tFinish) + local start, finish = m.getStartFinish(source) + if not start then + return false + end + return start <= tFinish and finish >= tStart +end + +function m.isBetweenRange(source, tStart, tFinish) + local start, finish = m.getRange(source) + if not start then + return false + end + return start <= tFinish and finish >= tStart +end + +--- 添加child +function m.addChilds(list, obj, map) + local keys = map[obj.type] + if keys then + for i = 1, #keys do + local key = keys[i] + if key == '#' then + for i = 1, #obj do + list[#list+1] = obj[i] + end + elseif obj[key] then + list[#list+1] = obj[key] + elseif type(key) == 'string' + and key:sub(1, 1) == '#' then + key = key:sub(2) + if obj[key] then + for i = 1, #obj[key] do + list[#list+1] = obj[key][i] + end + end + end + end + end +end + +--- 遍历所有包含offset的source +function m.eachSourceContain(ast, offset, callback) + local list = { ast } + local mark = {} + while true do + local len = #list + if len == 0 then + return + end + local obj = list[len] + list[len] = nil + if not mark[obj] then + mark[obj] = true + if m.isInRange(obj, offset) then + if m.isContain(obj, offset) then + local res = callback(obj) + if res ~= nil then + return res + end + end + m.addChilds(list, obj, m.childMap) + end + end + end +end + +--- 遍历所有在某个范围内的source +function m.eachSourceBetween(ast, start, finish, callback) + local list = { ast } + local mark = {} + while true do + local len = #list + if len == 0 then + return + end + local obj = list[len] + list[len] = nil + if not mark[obj] then + mark[obj] = true + if m.isBetweenRange(obj, start, finish) then + if m.isBetween(obj, start, finish) then + local res = callback(obj) + if res ~= nil then + return res + end + end + m.addChilds(list, obj, m.childMap) + end + end + end +end + +--- 遍历所有指定类型的source +function m.eachSourceType(ast, type, callback) + local cache = ast._typeCache + if not cache then + cache = {} + ast._typeCache = cache + m.eachSource(ast, function (source) + local tp = source.type + if not tp then + return + end + local myCache = cache[tp] + if not myCache then + myCache = {} + cache[tp] = myCache + end + myCache[#myCache+1] = source + end) + end + local myCache = cache[type] + if not myCache then + return + end + for i = 1, #myCache do + callback(myCache[i]) + end +end + +--- 遍历所有的source +function m.eachSource(ast, callback) + local list = { ast } + local mark = {} + local index = 1 + while true do + local obj = list[index] + if not obj then + return + end + list[index] = false + index = index + 1 + if not mark[obj] then + mark[obj] = true + callback(obj) + m.addChilds(list, obj, m.childMap) + end + end +end + +--- 获取指定的 special +function m.eachSpecialOf(ast, name, callback) + local root = m.getRoot(ast) + if not root.specials then + return + end + local specials = root.specials[name] + if not specials then + return + end + for i = 1, #specials do + callback(specials[i]) + end +end + +--- 获取偏移对应的坐标 +---@param lines table +---@return integer row +---@return integer col +function m.positionOf(lines, offset) + if offset < 1 then + return 0, 0 + end + local lastLine = lines[#lines] + if offset > lastLine.finish then + return #lines, offset - lastLine.start + end + local min = 1 + local max = #lines + for _ = 1, 100 do + if max <= min then + local line = lines[min] + return min, offset - line.start + 1 + end + local row = (max - min) // 2 + min + local line = lines[row] + if offset < line.start then + max = row - 1 + elseif offset > line.finish then + min = row + 1 + else + return row, offset - line.start + 1 + end + end + error('Stack overflow!') +end + +--- 获取坐标对应的偏移 +---@param lines table +---@param row integer +---@param col integer +---@return integer {name = 'offset'} +function m.offsetOf(lines, row, col) + if row < 1 then + return 0 + end + if row > #lines then + local lastLine = lines[#lines] + return lastLine.finish + end + local line = lines[row] + local len = line.finish - line.start + 1 + if col < 0 then + return line.start + elseif col > len then + return line.finish + else + return line.start + col - 1 + end +end + +function m.lineContent(lines, text, row, ignoreNL) + local line = lines[row] + if not line then + return '' + end + if ignoreNL then + return text:sub(line.start, line.range) + else + return text:sub(line.start, line.finish) + end +end + +function m.lineRange(lines, row, ignoreNL) + local line = lines[row] + if not line then + return 0, 0 + end + if ignoreNL then + return line.start, line.range + else + return line.start, line.finish + end +end + +function m.lineData(lines, row) + return lines[row] +end + +function m.getKeyTypeOfLiteral(obj) + if not obj then + return nil + end + local tp = obj.type + if tp == 'field' + or tp == 'method' then + return 'string' + elseif tp == 'string' then + return 'string' + elseif tp == 'number' then + return 'number' + elseif tp == 'boolean' then + return 'boolean' + end +end + +function m.getKeyType(obj) + if not obj then + return nil + end + local tp = obj.type + if tp == 'getglobal' + or tp == 'setglobal' then + return 'string' + elseif tp == 'local' + or tp == 'getlocal' + or tp == 'setlocal' then + return 'local' + elseif tp == 'getfield' + or tp == 'setfield' + or tp == 'tablefield' then + return 'string' + elseif tp == 'getmethod' + or tp == 'setmethod' then + return 'string' + elseif tp == 'getindex' + or tp == 'setindex' + or tp == 'tableindex' then + return m.getKeyTypeOfLiteral(obj.index) + elseif tp == 'field' + or tp == 'method' + or tp == 'doc.see.field' then + return 'string' + elseif tp == 'doc.class' then + return 'string' + elseif tp == 'doc.alias' then + return 'string' + elseif tp == 'doc.field' then + return 'string' + elseif tp == 'dummy' then + return 'string' + end + return m.getKeyTypeOfLiteral(obj) +end + +function m.getKeyNameOfLiteral(obj) + if not obj then + return nil + end + local tp = obj.type + if tp == 'field' + or tp == 'method' then + return obj[1] + elseif tp == 'string' then + local s = obj[1] + if s then + return s + end + elseif tp == 'number' then + local n = obj[1] + if n then + return ('%s'):format(util.viewLiteral(obj[1])) + end + elseif tp == 'boolean' then + local b = obj[1] + if b then + return tostring(b) + end + end +end + +function m.getKeyName(obj) + if not obj then + return nil + end + local tp = obj.type + if tp == 'getglobal' + or tp == 'setglobal' then + return obj[1] + elseif tp == 'local' + or tp == 'getlocal' + or tp == 'setlocal' then + return obj[1] + elseif tp == 'getfield' + or tp == 'setfield' + or tp == 'tablefield' then + if obj.field then + return obj.field[1] + end + elseif tp == 'getmethod' + or tp == 'setmethod' then + if obj.method then + return obj.method[1] + end + elseif tp == 'getindex' + or tp == 'setindex' + or tp == 'tableindex' then + return m.getKeyNameOfLiteral(obj.index) + elseif tp == 'field' + or tp == 'method' + or tp == 'doc.see.field' then + return obj[1] + elseif tp == 'doc.class' then + return obj.class[1] + elseif tp == 'doc.alias' then + return obj.alias[1] + elseif tp == 'doc.field' then + return obj.field[1] + elseif tp == 'dummy' then + return obj[1] + end + return m.getKeyNameOfLiteral(obj) +end + +function m.getSimpleName(obj) + if obj.type == 'call' then + local node = obj.node + if not node then + return + end + if node.special == 'rawset' + or node.special == 'rawget' then + local key = obj.args and obj.args[2] + return m.getKeyName(key) + end + return ('%p'):format(obj) + elseif obj.type == 'table' then + return ('%p'):format(obj) + elseif obj.type == 'select' then + return ('%p'):format(obj) + elseif obj.type == 'string' then + return ('%p'):format(obj) + elseif obj.type == 'doc.class.name' + or obj.type == 'doc.type.name' + or obj.type == 'doc.see.name' then + return ('%s'):format(obj[1]) + elseif obj.type == 'doc.class' then + return ('%s'):format(obj.class[1]) + end + return m.getKeyName(obj) +end + +--- 测试 a 到 b 的路径(不经过函数,不考虑 goto), +--- 每个路径是一个 block 。 +--- +--- 如果 a 在 b 的前面,返回 `"before"` 加上 2个`list<block>` +--- +--- 如果 a 在 b 的后面,返回 `"after"` 加上 2个`list<block>` +--- +--- 否则返回 `false` +--- +--- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。 +---@param a table +---@param b table +---@return string|boolean mode +---@return table pathA? +---@return table pathB? +function m.getPath(a, b, sameFunction) + --- 首先测试双方在同一个函数内 + if sameFunction and m.getParentFunction(a) ~= m.getParentFunction(b) then + return false + end + local mode + local objA + local objB + if a.finish < b.start then + mode = 'before' + objA = a + objB = b + elseif a.start > b.finish then + mode = 'after' + objA = b + objB = a + else + return 'equal', {}, {} + end + local pathA = {} + local pathB = {} + for _ = 1, 1000 do + objA = m.getParentBlock(objA) + pathA[#pathA+1] = objA + if (not sameFunction and objA.type == 'function') or objA.type == 'main' then + break + end + end + for _ = 1, 1000 do + objB = m.getParentBlock(objB) + pathB[#pathB+1] = objB + if (not sameFunction and objA.type == 'function') or objB.type == 'main' then + break + end + end + -- pathA: {1, 2, 3, 4, 5} + -- pathB: {5, 6, 2, 3} + local top = #pathB + local start + for i = #pathA, 1, -1 do + local currentBlock = pathA[i] + if currentBlock == pathB[top] then + start = i + break + end + end + if not start then + return nil + end + -- pathA: { 1, 2, 3} + -- pathB: {5, 6, 2, 3} + local extra = 0 + local align = top - start + for i = start, 1, -1 do + local currentA = pathA[i] + local currentB = pathB[i+align] + if currentA ~= currentB then + extra = i + break + end + end + -- pathA: {1} + local resultA = {} + for i = extra, 1, -1 do + resultA[#resultA+1] = pathA[i] + end + -- pathB: {5, 6} + local resultB = {} + for i = extra + align, 1, -1 do + resultB[#resultB+1] = pathB[i] + end + return mode, resultA, resultB +end + +-- 根据语法,单步搜索定义 +local function stepRefOfLocal(loc, mode) + local results = {} + if loc.start ~= 0 then + results[#results+1] = loc + end + local refs = loc.ref + if not refs then + return results + end + for i = 1, #refs do + local ref = refs[i] + if ref.start == 0 then + goto CONTINUE + end + if mode == 'def' then + if ref.type == 'local' + or ref.type == 'setlocal' then + results[#results+1] = ref + end + else + if ref.type == 'local' + or ref.type == 'setlocal' + or ref.type == 'getlocal' then + results[#results+1] = ref + end + end + ::CONTINUE:: + end + return results +end + +local function stepRefOfLabel(label, mode) + local results = { label } + if not label or mode == 'def' then + return results + end + local refs = label.ref + if not refs then + return results + end + for i = 1, #refs do + local ref = refs[i] + results[#results+1] = ref + end + return results +end + +local function stepRefOfDocType(status, obj, mode) + local results = {} + if obj.type == 'doc.class.name' + or obj.type == 'doc.type.name' + or obj.type == 'doc.alias.name' + or obj.type == 'doc.extends.name' + or obj.type == 'doc.see.name' then + local name = obj[1] + if not name or not status.interface.docType then + return results + end + if name == 'nil' + or name == 'any' + or name == 'boolean' + or name == 'string' + or name == 'table' + or name == 'number' + or name == 'integer' + or name == 'function' + or name == 'table' + or name == 'thread' + or name == 'userdata' + or name == 'lightuserdata' then + mode = 'def' + end + local docs = status.interface.docType(name) + for i = 1, #docs do + local doc = docs[i] + if mode == 'def' then + if doc.type == 'doc.class.name' + or doc.type == 'doc.alias.name' then + results[#results+1] = doc + end + else + results[#results+1] = doc + end + end + else + results[#results+1] = obj + end + return results +end + +function m.getStepRef(status, obj, mode) + if obj.type == 'getlocal' + or obj.type == 'setlocal' then + return stepRefOfLocal(obj.node, mode) + end + if obj.type == 'local' then + return stepRefOfLocal(obj, mode) + end + if obj.type == 'label' then + return stepRefOfLabel(obj, mode) + end + if obj.type == 'goto' then + return stepRefOfLabel(obj.node, mode) + end + if obj.type == 'doc.class.name' + or obj.type == 'doc.type.name' + or obj.type == 'doc.extends.name' + or obj.type == 'doc.alias.name' then + return stepRefOfDocType(status, obj, mode) + end + if obj.type == 'function' then + return { obj } + end + return nil +end + +-- 根据语法,单步搜索field +local function stepFieldOfLocal(loc) + local results = {} + local refs = loc.ref + for i = 1, #refs do + local ref = refs[i] + if ref.type == 'setglobal' + or ref.type == 'getglobal' then + results[#results+1] = ref + elseif ref.type == 'getlocal' then + local nxt = ref.next + if nxt then + if nxt.type == 'setfield' + or nxt.type == 'getfield' + or nxt.type == 'setmethod' + or nxt.type == 'getmethod' + or nxt.type == 'setindex' + or nxt.type == 'getindex' then + results[#results+1] = nxt + end + end + end + end + return results +end +local function stepFieldOfTable(tbl) + local result = {} + for i = 1, #tbl do + result[i] = tbl[i] + end + return result +end +function m.getStepField(obj) + if obj.type == 'getlocal' + or obj.type == 'setlocal' then + return stepFieldOfLocal(obj.node) + end + if obj.type == 'local' then + return stepFieldOfLocal(obj) + end + if obj.type == 'table' then + return stepFieldOfTable(obj) + end +end + +local function convertSimpleList(list) + local simple = {} + for i = #list, 1, -1 do + local c = list[i] + if c.type == 'getglobal' + or c.type == 'setglobal' then + if c.special == '_G' then + simple.mode = 'global' + goto CONTINUE + end + local loc = c.node + if loc.special == '_G' then + simple.mode = 'global' + if not simple.node then + simple.node = c + end + else + simple.mode = 'local' + simple[#simple+1] = m.getSimpleName(loc) + if not simple.node then + simple.node = loc + end + end + elseif c.type == 'getlocal' + or c.type == 'setlocal' then + if c.special == '_G' then + simple.mode = 'global' + goto CONTINUE + end + simple.mode = 'local' + if not simple.node then + simple.node = c.node + end + elseif c.type == 'local' then + simple.mode = 'local' + if not simple.node then + simple.node = c + end + else + if not simple.node then + simple.node = c + end + end + simple[#simple+1] = m.getSimpleName(c) or m.ANY + ::CONTINUE:: + end + if simple.mode == 'global' and #simple == 0 then + simple[1] = '_G' + simple.node = list[#list] + end + return simple +end + +-- 搜索 `a.b.c` 的等价表达式 +local function buildSimpleList(obj, max) + local list = {} + local cur = obj + local limit = max and (max + 1) or 11 + for i = 1, max or limit do + if i == limit then + return nil + end + while cur.type == 'paren' do + cur = cur.exp + if not cur then + return nil + end + end + if cur.type == 'setfield' + or cur.type == 'getfield' + or cur.type == 'setmethod' + or cur.type == 'getmethod' + or cur.type == 'setindex' + or cur.type == 'getindex' then + list[i] = cur + cur = cur.node + elseif cur.type == 'tablefield' + or cur.type == 'tableindex' then + list[i] = cur + cur = cur.parent.parent + if cur.type == 'return' then + list[i+1] = list[i].parent + break + end + elseif cur.type == 'getlocal' + or cur.type == 'setlocal' + or cur.type == 'local' then + list[i] = cur + break + elseif cur.type == 'setglobal' + or cur.type == 'getglobal' then + list[i] = cur + break + elseif cur.type == 'select' + or cur.type == 'table' + or cur.type == 'call' then + list[i] = cur + break + elseif cur.type == 'string' then + list[i] = cur + break + elseif cur.type == '...' then + list[i] = cur + break + elseif cur.type == 'doc.class.name' + or cur.type == 'doc.type.name' + or cur.type == 'doc.class' + or cur.type == 'doc.see.name' then + list[i] = cur + break + elseif cur.type == 'doc.see.field' then + list[i] = cur + cur = cur.parent.name + elseif cur.type == 'function' + or cur.type == 'main' then + break + else + return nil + end + end + return convertSimpleList(list) +end + +function m.getSimple(obj, max) + local simpleList + if obj.type == 'getfield' + or obj.type == 'setfield' + or obj.type == 'getmethod' + or obj.type == 'setmethod' + or obj.type == 'getindex' + or obj.type == 'setindex' + or obj.type == 'local' + or obj.type == 'getlocal' + or obj.type == 'setlocal' + or obj.type == 'setglobal' + or obj.type == 'getglobal' + or obj.type == 'tablefield' + or obj.type == 'tableindex' + or obj.type == 'select' + or obj.type == 'call' + or obj.type == 'table' + or obj.type == 'string' + or obj.type == '...' + or obj.type == 'doc.class.name' + or obj.type == 'doc.class' + or obj.type == 'doc.type.name' + or obj.type == 'doc.see.name' + or obj.type == 'doc.see.field' then + simpleList = buildSimpleList(obj, max) + elseif obj.type == 'field' + or obj.type == 'method' then + simpleList = buildSimpleList(obj.parent, max) + end + return simpleList +end + +---Create a new status +---@param parentStatus core.guide.status +---@param interface table +---@param deep boolean +---@return core.guide.status +function m.status(parentStatus, interface, deep) + ---@class core.guide.status + local status = { + share = parentStatus and parentStatus.share or { + count = 0, + cacheLock = {}, + }, + depth = parentStatus and (parentStatus.depth + 1) or 0, + searchDeep= parentStatus and parentStatus.searchDeep or deep or -999, + interface = parentStatus and parentStatus.interface or {}, + deep = parentStatus and parentStatus.deep, + clock = parentStatus and parentStatus.clock or osClock(), + results = {}, + } + if interface then + for k, v in pairs(interface) do + status.interface[k] = v + end + end + status.deep = status.depth <= status.searchDeep + return status +end + +function m.copyStatusResults(a, b) + local ra = a.results + local rb = b.results + for i = 1, #rb do + ra[#ra+1] = rb[i] + end +end + +function m.isGlobal(source) + if source.type == 'setglobal' + or source.type == 'getglobal' then + if source.node and source.node.tag == '_ENV' then + return true + end + end + if source.type == 'field' then + source = source.parent + end + if source.type == 'getfield' + or source.type == 'setfield' then + local node = source.node + if node and node.special == '_G' then + return true + end + end + return false +end + +function m.isDoc(source) + return source.type:sub(1, 4) == 'doc.' +end + +function m.isDocClass(source) + return source.type == 'doc.class' +end + +function m.isSet(source) + local tp = source.type + if tp == 'setglobal' + or tp == 'local' + or tp == 'setlocal' + or tp == 'setfield' + or tp == 'setmethod' + or tp == 'setindex' + or tp == 'tablefield' + or tp == 'tableindex' then + return true + end + if tp == 'call' then + local special = m.getSpecial(source.node) + if special == 'rawset' then + return true + end + end + return false +end + +function m.isGet(source) + local tp = source.type + if tp == 'getglobal' + or tp == 'getlocal' + or tp == 'getfield' + or tp == 'getmethod' + or tp == 'getindex' then + return true + end + if tp == 'call' then + local special = m.getSpecial(source.node) + if special == 'rawget' then + return true + end + end + return false +end + +function m.getSpecial(source) + if not source then + return nil + end + return source.special +end + +--- 根据函数的调用参数,获取:调用,参数索引 +function m.getCallAndArgIndex(callarg) + local callargs = callarg.parent + if not callargs or callargs.type ~= 'callargs' then + return nil + end + local index + for i = 1, #callargs do + if callargs[i] == callarg then + index = i + break + end + end + local call = callargs.parent + return call, index +end + +--- 根据函数调用的返回值,获取:调用的函数,参数列表,自己是第几个返回值 +function m.getCallValue(source) + local value = m.getObjectValue(source) or source + if not value then + return + end + local call, index + if value.type == 'call' then + call = value + index = 1 + elseif value.type == 'select' then + call = value.vararg + index = value.index + if call.type ~= 'call' then + return + end + else + return + end + if call.node.special == 'pcall' + or call.node.special == 'xpcall' then + return call.args and call.args[1], call.args, index - 1 + end + return call.node, call.args, index +end + +function m.getNextRef(ref) + local nextRef = ref.next + if nextRef then + if nextRef.type == 'setfield' + or nextRef.type == 'getfield' + or nextRef.type == 'setmethod' + or nextRef.type == 'getmethod' + or nextRef.type == 'setindex' + or nextRef.type == 'getindex' then + return nextRef + end + end + -- 穿透 rawget 与 rawset + local call, index = m.getCallAndArgIndex(ref) + if call then + if call.node.special == 'rawset' and index == 1 then + return call + end + if call.node.special == 'rawget' and index == 1 then + return call + end + end + + return nil +end + +function m.checkSameSimpleInValueOfTable(status, value, start, pushQueue) + if value.type ~= 'table' then + return + end + for i = 1, #value do + local field = value[i] + pushQueue(field, start + 1) + end +end + +function m.checkStatusDepth(status) + if status.depth <= 20 then + return true + end + if m.debugMode then + error('status.depth overflow') + elseif DEVELOP then + --log.warn(debug.traceback('status.depth overflow')) + logWarn('status.depth overflow') + end + return false +end + +function m.searchFields(status, obj, key) + if not m.checkStatusDepth(status) then + return + end + local simple = m.getSimple(obj) + if not simple then + return + end + simple[#simple+1] = key or m.ANY + m.searchSameFields(status, simple, 'field') + m.cleanResults(status.results) +end + +function m.searchDefFields(status, obj, key) + if not m.checkStatusDepth(status) then + return + end + local simple = m.getSimple(obj) + if not simple then + return + end + simple[#simple+1] = key or m.ANY + m.searchSameFields(status, simple, 'deffield') + m.cleanResults(status.results) +end + +---@param obj parser.guide.object +---@return parser.guide.object +function m.getObjectValue(obj) + while obj.type == 'paren' do + obj = obj.exp + if not obj then + return nil + end + end + if obj.type == 'boolean' + or obj.type == 'number' + or obj.type == 'integer' + or obj.type == 'string' + or obj.type == 'doc.type.table' + or obj.type == 'doc.type.arrary' then + return obj + end + if obj.value then + return obj.value + end + if obj.type == 'field' + or obj.type == 'method' then + return obj.parent and obj.parent.value + end + if obj.type == 'call' then + if obj.node.special == 'rawset' then + return obj.args and obj.args[3] + else + return obj + end + end + if obj.type == 'select' then + return obj + end + return nil +end + +function m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) + local cache, makeCache = m.getRefCache(status, mt, '__index') + if cache then + for _, obj in ipairs(cache) do + pushQueue(obj, start, true) + end + return + end + cache = {} + local newStatus = m.status(status) + m.searchDefFields(newStatus, mt, '__index') + local refsStatus = m.status(status) + for i = 1, #newStatus.results do + local indexValue = m.getObjectValue(newStatus.results[i]) + if indexValue then + m.searchRefs(refsStatus, indexValue, 'ref') + end + end + for i = 1, #refsStatus.results do + local obj = refsStatus.results[i] + pushQueue(obj, start, true) + cache[i] = obj + end + if makeCache then + makeCache(cache) + end +end +function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) + if not func or func.special ~= 'setmetatable' then + return + end + local call = func.parent + local args = call.args + if not args then + return + end + local obj = args[1] + local mt = args[2] + if obj then + pushQueue(obj, start, true) + local newStatus = m.status(status) + m.searchRefs(newStatus, obj, 'def') + for _, def in ipairs(newStatus.results) do + pushQueue(def, start, true) + end + end + if mt then + if not status.share.markMetaTable then + status.share.markMetaTable = {} + end + if status.share.markMetaTable[mt] then + return + end + status.share.markMetaTable[mt] = true + m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) + status.share.markMetaTable[mt] = nil + end +end + +function m.checkSameSimpleInValueOfCallMetaTable(status, call, start, pushQueue) + if status.crossMetaTableMark then + return + end + status.crossMetaTableMark = true + if call.type == 'call' then + m.checkSameSimpleInValueOfSetMetaTable(status, call.node, start, pushQueue) + end + status.crossMetaTableMark = false +end + +function m.checkSameSimpleInSpecialBranch(status, obj, start, pushQueue) + if status.interface.index then + local results = status.interface.index(obj) + if not results then + return + end + for _, res in ipairs(results) do + pushQueue(obj, start + 1) + end + end +end + +function m.checkSameSimpleInParamSelf(status, obj, start, pushQueue) + if obj.type ~= 'getlocal' or obj[1] ~= 'self' then + return + end + local node = obj.node + if node.tag == 'self' then + return + end + if node.parent.type ~= 'funcargs' then + return + end + local func = node.parent.parent + if func.type ~= 'function' or func.parent.type ~= 'setfield' then + return + end + local fieldNode = func.parent.node + local newStatus = m.status(status) + m.searchRefs(newStatus, fieldNode, 'ref') + for _, ref in ipairs(newStatus.results) do + pushQueue(ref, start, true) + end +end + +local function appendValidGenericType(results, status, typeName, obj) + if typeName.parent.type == 'doc.type.typeliteral' then + if obj.type == 'string' and status.interface.docType then + local docs = status.interface.docType(obj[1]) + for i = 1, #docs do + local doc = docs[i] + if doc.type == 'doc.class.name' + or doc.type == 'doc.alias.name' then + results[#results+1] = doc + break + end + end + end + else + -- 发现没有使用 `T`,则沿用既有逻辑直接返回实参 + results[#results+1] = obj + end +end + +local function stepRefOfGenericCrossTable(status, doc, typeName) + for _, typeUnit in ipairs(doc.extends.types) do + if typeUnit.type == 'doc.type.table' then + for _, where in ipairs {'key', 'value'} do + local childTypes = typeUnit[where].types + for _, childName in ipairs(childTypes) do + if childName[1] == typeName[1] then + return function (obj) + local childStatus = m.status(status) + m.searchRefs(childStatus, obj, 'def') + for _, res in ipairs(childStatus.results) do + if res.type == 'doc.type.table' then + return res[where] + end + if res.type == 'doc.type.array' then + if where == 'key' then + return status.interface and status.interface.docType('integer')[1] + end + if where == 'value' then + return res.node + end + end + end + end + end + end + end + return function (obj) + return nil + end + elseif typeUnit.type == 'doc.type.array' then + return function (obj) + local childStatus = m.status(status) + m.searchRefs(childStatus, obj, 'def') + for _, res in ipairs(childStatus.results) do + if res.type == 'doc.type.array' then + return res.node + end + end + end + end + end + return nil +end + +local function getIteratorArg(status, args, index) + local call = args.parent + local node = call.node + if not node.iterator then + return nil + end + if node.type ~= 'call' then + return nil + end + local results = m.checkSameSimpleInCallInSameFile(status, node.node, node.args, index + 1) + return results[1] +end + +local function stepRefOfGeneric(status, typeUnit, args, mode) + local results = {} + if not args then + return results + end + local myName = typeUnit[1] + for _, typeName in ipairs(typeUnit.typeGeneric[myName]) do + if typeName == typeUnit then + goto CONTINUE + end + local docArg = m.getParentType(typeName, 'doc.type.arg') + or m.getParentType(typeName, 'doc.param') + or m.getParentType(typeName, 'doc.type.array') + if not docArg then + goto CONTINUE + end + local doc = m.getDocState(docArg) + if not doc.bindSources then + goto CONTINUE + end + local crossTable = stepRefOfGenericCrossTable(status, docArg, typeName) + + -- find out param index + local genericIndex + if docArg.type == 'doc.param' then + local paramName = docArg.param[1] + for _, source in ipairs(doc.bindSources) do + if source.type == 'function' + and source.args then + for i, arg in ipairs(source.args) do + if arg[1] == paramName then + genericIndex = i + break + end + end + end + end + elseif docArg.type == 'doc.type.arg' then + for index, arg in ipairs(docArg.parent.args) do + if arg == docArg then + genericIndex = index + break + end + end + end + + local callArg = args[genericIndex] + or getIteratorArg(status, args, genericIndex) + + if not callArg then + goto CONTINUE + end + if crossTable then + callArg = crossTable(callArg) + if not callArg then + goto CONTINUE + end + end + appendValidGenericType(results, status, typeName, callArg) + ::CONTINUE:: + end + return results +end + +function m.checkSameSimpleByDocType(status, doc, args) + if status.share.searchingBindedDoc then + return + end + if doc.type ~= 'doc.type' then + return + end + local results = {} + for _, piece in ipairs(doc.types) do + if piece.typeGeneric then + local pieceResult = stepRefOfGeneric(status, piece, args, 'def') + for _, res in ipairs(pieceResult) do + results[#results+1] = res + end + else + local pieceResult = stepRefOfDocType(status, piece, 'def') + for _, res in ipairs(pieceResult) do + results[#results+1] = res + end + end + end + return results +end + +function m.checkSameSimpleByBindDocs(status, obj, start, pushQueue, mode) + if not obj.bindDocs then + return + end + if status.share.searchingBindedDoc then + return + end + local skipInfer = false + local results = {} + for _, doc in ipairs(obj.bindDocs) do + if doc.type == 'doc.class' then + results[#results+1] = doc + elseif doc.type == 'doc.type' then + results[#results+1] = doc + elseif doc.type == 'doc.param' then + -- function (x) 的情况 + if obj.type == 'local' + and m.getKeyName(obj) == doc.param[1] then + if obj.parent.type == 'funcargs' + or obj.parent.type == 'in' + or obj.parent.type == 'loop' then + results[#results+1] = doc.extends + end + end + elseif doc.type == 'doc.field' then + results[#results+1] = doc + elseif doc.type == 'doc.vararg' then + if obj.type == '...' then + results[#results+1] = doc + end + end + end + for _, res in ipairs(results) do + if res.type == 'doc.class' + or res.type == 'doc.type' + or res.type == 'doc.vararg' then + pushQueue(res, start, true) + skipInfer = true + end + if res.type == 'doc.type.function' then + pushQueue(res, start, true) + elseif res.type == 'doc.field' then + pushQueue(res, start + 1) + end + end + return skipInfer +end + +function m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) + if status.share.searchingBindedDoc then + return + end + if not obj.bindSources then + return + end + status.share.searchingBindedDoc = true + local mark = {} + local newStatus = m.status(status) + for _, ref in ipairs(obj.bindSources) do + if not mark[ref] then + mark[ref] = true + m.searchRefs(newStatus, ref, mode) + end + end + status.share.searchingBindedDoc = nil + for _, res in ipairs(newStatus.results) do + pushQueue(res, start, true) + end +end + +function m.checkSameSimpleOfRefByDocReturn(status, obj, start, pushQueue, mode) + if status.share.searchingBindedDoc then + return + end + if not obj.bindSources then + return + end + local index = 0 + for _, doc in ipairs(obj.bindGroup) do + if doc.type == 'doc.return' then + index = index + 1 + if doc == obj then + break + end + end + end + status.share.searchingBindedDoc = true + local mark = {} + local newStatus = m.status(status) + for _, ref in ipairs(obj.bindSources) do + if not mark[ref] then + mark[ref] = true + m.searchRefs(newStatus, ref, mode) + end + end + status.share.searchingBindedDoc = nil + for _, res in ipairs(newStatus.results) do + local call = res.parent + if call.type == 'call' then + if index == 1 then + local sel = call.parent + if sel.type == 'select' and sel.index == index then + pushQueue(sel.parent, start, true) + end + else + if call.extParent then + for _, sel in ipairs(call.extParent) do + if sel.type == 'select' and sel.index == index then + pushQueue(sel.parent, start, true) + end + end + end + end + end + end +end + +local function getArrayOrTableLevel(obj) + local level = 0 + while true do + local parent = obj.parent + if parent.type == 'doc.type.array' then + level = level + 1 + elseif parent.type == 'doc.type.table' then + if obj.type == 'doc.type' then + level = level + 1 + -- else 只存在 obj.type == 'doc.type.name' 的情况,即 table<k,v> 中的 table,这种是不需要再增加层级的 + end + elseif parent.type == 'doc.type' and parent.parent and parent.parent.type == 'doc.type.table' then + level = level + 1 + parent = parent.parent + else + break + end + obj = parent + end + return level +end + +function m.checkSameSimpleByDoc(status, obj, start, pushQueue, mode) + if obj.type == 'doc.class.name' + or obj.type == 'doc.class' then + if obj.type == 'doc.class.name' then + obj = m.getDocState(obj) + end + local classStart + for _, doc in ipairs(obj.bindGroup) do + if doc == obj then + classStart = true + elseif doc.type == 'doc.class' then + classStart = false + end + if classStart and doc.type == 'doc.field' then + pushQueue(doc, start + 1) + end + end + m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) + if mode == 'ref' then + local pieceResult = stepRefOfDocType(status, obj.class, 'ref') + for _, res in ipairs(pieceResult) do + pushQueue(res, start, true) + end + if obj.extends then + for _, ext in ipairs(obj.extends) do + local pieceResult = stepRefOfDocType(status, ext, 'def') + for _, res in ipairs(pieceResult) do + pushQueue(res, start, true) + end + end + end + end + return true + elseif obj.type == 'doc.type' then + for _, piece in ipairs(obj.types) do + local pieceResult = stepRefOfDocType(status, piece, 'def') + for _, res in ipairs(pieceResult) do + pushQueue(res, start, true) + end + end + if mode == 'ref' then + m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) + end + return true + elseif obj.type == 'doc.type.name' + or obj.type == 'doc.see.name' then + local pieceResult = stepRefOfDocType(status, obj, 'def') + for _, res in ipairs(pieceResult) do + pushQueue(res, start, true) + end + + if mode == 'ref' then + local state = m.getDocState(obj) + if state.type == 'doc.type' then + m.checkSameSimpleOfRefByDocSource(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode) + end + if state.type == 'doc.return' then + m.checkSameSimpleOfRefByDocReturn(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode) + end + end + return true + elseif obj.type == 'doc.field' then + if mode ~= 'field' + and mode ~= 'deffield' then + return m.checkSameSimpleByDoc(status, obj.extends, start, pushQueue, mode) + end + elseif obj.type == 'doc.type.array' then + pushQueue(obj.node, start + 1, true) + return true + elseif obj.type == 'doc.type.table' then + pushQueue(obj.node, start, true) + pushQueue(obj.value, start + 1, true) + return true + elseif obj.type == 'doc.vararg' then + pushQueue(obj.vararg, start, true) + end +end + +function m.checkSameSimpleInArg1OfSetMetaTable(status, obj, start, pushQueue) + local args = obj.parent + if not args or args.type ~= 'callargs' then + return + end + local callNode = args.parent.node + if callNode.special ~= 'setmetatable' then + return + end + if args[1] ~= obj then + return + end + local mt = args[2] + if mt then + if m.hasValueMark(status, mt) then + return + end + m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) + end +end + +function m.searchSameMethodOutSelf(ref, mark) + local selfNode + if ref.tag == 'self' then + selfNode = ref + else + if ref.type == 'getlocal' + or ref.type == 'setlocal' then + local node = ref.node + if node.tag == 'self' then + selfNode = node + end + end + end + if selfNode then + if mark[selfNode] then + return nil + end + mark[selfNode] = true + local method = selfNode.method.node + if mark[method] then + return nil + end + mark[method] = true + return method + end +end + +function m.searchSameMethodIntoSelf(ref, mark) + local nxt = ref.next + if not nxt then + return nil + end + if nxt.type ~= 'setmethod' then + return nil + end + if mark[ref] then + return nil + end + mark[ref] = true + local value = nxt.value + if not value or value.type ~= 'function' then + return nil + end + local selfRef = value.locals and value.locals[1] + if not selfRef or selfRef.tag ~= 'self' then + return nil + end + if mark[selfRef] then + return nil + end + mark[selfRef] = true + return selfRef +end + +function m.searchSameFieldsCrossMethod(status, ref, start, pushQueue, mode) + if status.share.crossMethodMark2 then + return + end + local mark = status.crossMethodMark + if not mark then + mark = {} + status.crossMethodMark = mark + end + if mark[ref] then + return + end + local selfRef = m.searchSameMethodIntoSelf(ref, mark) + if selfRef then + tracy.ZoneBeginN 'searchSameFieldsCrossMethod' + local _ <close> = tracy.ZoneEnd + -- 如果自己是method,则只检查自己内部的self引用 + status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1 + status.share.crossMethodMark2 = true + local newStatus = m.status(status) + m.searchRefs(newStatus, selfRef, mode) + for _, res in ipairs(newStatus.results) do + pushQueue(res, start, true) + end + status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1 + status.share.crossMethodMark2 = nil + return + end + local method = m.searchSameMethodOutSelf(ref, mark) + if method then + pushQueue(method, start, true) + return + end +end + +local function checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, source, index, args) + source = m.getObjectValue(source) or source + if not source or source.type ~= 'function' then + return + end + if not source.bindDocs then + return + end + local returns = {} + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.return' then + for _, rtn in ipairs(doc.returns) do + returns[#returns+1] = rtn + end + end + end + local rtn = returns[index] + if not rtn then + return + end + local types = m.checkSameSimpleByDocType(status, rtn, args) + if not types then + return + end + for _, res in ipairs(types) do + results[#results+1] = res + end + return true +end + +local function checkSameSimpleAndMergeDocFunctionReturn(status, results, docFunc, index, args) + if docFunc.type ~= 'doc.type.function' then + return + end + local rtn = docFunc.returns[index] + if rtn then + local types = m.checkSameSimpleByDocType(status, rtn, args) + if types then + for _, res in ipairs(types) do + results[#results+1] = res + end + return true + end + end +end + +local function checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, source, index) + if not source.bindDocs then + return + end + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.type' then + for _, typeUnit in ipairs(doc.types) do + if checkSameSimpleAndMergeDocFunctionReturn(status, results, typeUnit, index) then + return true + end + end + end + end +end + +function m.checkSameSimpleInCallInSameFile(status, func, args, index) + if not status.share.callResultsCache then + status.share.callResultsCache = {} + end + local cache = status.share.callResultsCache[func] + if not cache then + cache = {} + status.share.callResultsCache[func] = cache + end + local results = cache[index] + if results then + return results + end + results = {} + if func.special then + --return results + end + local newStatus = m.status(status) + m.searchRefs(newStatus, func, 'def') + local hasDocReturn + for _, def in ipairs(newStatus.results) do + hasDocReturn = checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, def, index) + or checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, def, index, args) + or checkSameSimpleAndMergeDocFunctionReturn(status, results, def, index, args) + or hasDocReturn + end + if not hasDocReturn then + for _, def in ipairs(newStatus.results) do + local value = m.getObjectValue(def) or def + if value.type == 'function' then + local returns = value.returns + if returns then + for _, ret in ipairs(returns) do + local exp = ret[index] + if exp then + results[#results+1] = exp + end + end + end + end + end + cache[index] = results + end + return results +end + +function m.checkSameSimpleInCall(status, ref, start, pushQueue, mode) + if status.share.inBeSetValue and status.share.inBeSetValue > 0 then + return + end + if status.share.inSetValue and status.share.inSetValue > 5 then + return + end + local func, args, index = m.getCallValue(ref) + if not func then + return + end + if m.checkCallMark(status, func.parent, true) then + return + end + status.share.inSetValue = (status.share.inSetValue or 0) + 1 + -- 检查赋值是 semetatable() 的情况 + m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) + local objs = m.checkSameSimpleInCallInSameFile(status, func, args, index) + if status.interface.call then + local cobjs = status.interface.call(func, args, index) + if cobjs then + for _, obj in ipairs(cobjs) do + if not m.checkReturnMark(status, obj) then + objs[#objs+1] = obj + end + end + end + end + m.cleanResults(objs) + if not status.share.callFuncMark then + status.share.callFuncMark = {} + end + local mark = {} + for _, obj in ipairs(objs) do + if mark[obj] then + goto CONTINUE + end + if status.share.callFuncMark[obj] then + goto CONTINUE + end + status.share.callFuncMark[obj] = true + local newStatus = m.status(status) + m.searchRefs(newStatus, obj, mode) + pushQueue(obj, start, true) + mark[obj] = true + for _, obj in ipairs(newStatus.results) do + pushQueue(obj, start, true) + mark[obj] = true + end + status.share.callFuncMark[obj] = nil + ::CONTINUE:: + end + status.share.inSetValue = (status.share.inSetValue or 0) - 1 +end + +local function searchRawset(ref, results) + if m.getKeyName(ref) ~= 'rawset' then + return + end + local call = ref.parent + if call.type ~= 'call' or call.node ~= ref then + return + end + if not call.args then + return + end + local arg1 = call.args[1] + if arg1.special ~= '_G' then + -- 不会吧不会吧,不会真的有人写成 `rawset(_G._G._G, 'xxx', value)` 吧 + return + end + results[#results+1] = call +end + +local function searchG(ref, results) + while ref and m.getKeyName(ref) == '_G' do + results[#results+1] = ref + ref = ref.next + end + if ref then + results[#results+1] = ref + searchRawset(ref, results) + end +end + +local function searchEnvRef(ref, results) + if ref.type == 'setglobal' + or ref.type == 'getglobal' then + results[#results+1] = ref + searchG(ref, results) + elseif ref.type == 'getlocal' then + results[#results+1] = ref.next + searchG(ref.next, results) + end +end + +function m.findGlobals(ast) + local root = m.getRoot(ast) + local results = {} + local env = m.getENV(root) + if env.ref then + for _, ref in ipairs(env.ref) do + searchEnvRef(ref, results) + end + end + return results +end + +function m.findGlobalsOfName(ast, name) + local root = m.getRoot(ast) + local results = {} + local globals = m.findGlobals(root) + for _, global in ipairs(globals) do + if m.getKeyName(global) == name then + results[#results+1] = global + end + end + return results +end + +function m.checkSameSimpleInGlobal(status, source) + local name = m.getKeyName(source) + if not name then + return + end + local objs + if status.interface.global then + objs = status.interface.global(name) + else + objs = m.findGlobalsOfName(source, name) + end + return objs +end + +function m.hasValueMark(status, value) + if not status.share.valueMark then + status.share.valueMark = {} + end + if status.share.valueMark[value] then + return true + end + status.share.valueMark[value] = true + return false +end + +function m.checkCallMark(status, a, mark) + if not status.share.callMark then + status.share.callMark = {} + end + if mark then + status.share.callMark[a] = mark + else + return status.share.callMark[a] + end + return false +end + +function m.checkReturnMark(status, a, mark) + if not status.share.returnMark then + status.share.returnMark = {} + end + local result = status.share.returnMark[a] + if mark then + status.share.returnMark[a] = mark + end + return result +end + +function m.searchSameFieldsInValue(status, ref, start, pushQueue, mode) + if status.share.inBeSetValue and status.share.inBeSetValue > 0 then + return + end + if status.share.inSetValue and status.share.inSetValue > 5 then + return + end + local value = m.getObjectValue(ref) + if not value then + return + end + if m.hasValueMark(status, value) then + return + end + status.share.inSetValue = (status.share.inSetValue or 0) + 1 + if not status.share.tempValueMark then + status.share.tempValueMark = {} + end + if not status.share.tempValueMark[value] then + status.share.tempValueMark[value] = true + local newStatus = m.status(status) + m.searchRefs(newStatus, value, mode) + status.share.tempValueMark[value] = nil + for _, res in ipairs(newStatus.results) do + pushQueue(res, start, true) + end + pushQueue(value, start, true) + end + status.share.inSetValue = (status.share.inSetValue or 0) - 1 + -- 检查形如 a = f() 的分支情况 + m.checkSameSimpleInCall(status, value, start, pushQueue, mode) +end + +function m.checkSameSimpleAsTableField(status, ref, start, pushQueue) + if not status.deep then + --return + end + local parent = ref.parent + if not parent or parent.type ~= 'tablefield' then + return + end + if m.hasValueMark(status, ref) then + return + end + local newStatus = m.status(status) + m.searchRefs(newStatus, parent.field, 'ref') + for _, res in ipairs(newStatus.results) do + pushQueue(res, start, true) + end +end + +function m.checkSearchLevel(status) + status.share.back = status.share.back or 0 + if status.share.back >= (status.interface.searchLevel or 0) then + -- TODO 限制向前搜索的次数 + --return true + end + status.share.back = status.share.back + 1 + return false +end + +function m.checkSameSimpleAsReturn(status, ref, start, pushQueue) + if not status.deep then + return + end + if not ref.parent or ref.parent.type ~= 'return' then + return + end + if ref.parent.parent.type ~= 'main' then + return + end + if m.checkSearchLevel(status) then + return + end + local newStatus = m.status(status) + m.searchRefsAsFunctionReturn(newStatus, ref, 'ref') + for _, res in ipairs(newStatus.results) do + if not m.checkCallMark(status, res) then + pushQueue(res, start, true) + end + end +end + +function m.checkSameSimpleAsSetValue(status, ref, start, pushQueue) + if not status.deep then + --return + end + if status.share.inSetValue and status.share.inSetValue > 0 then + return + end + if status.share.inBeSetValue and status.share.inBeSetValue > 5 then + return + end + if ref.type == 'select' then + return + end + local parent = ref.parent + if not parent then + return + end + if m.getObjectValue(parent) ~= ref then + return + end + if m.hasValueMark(status, ref) then + return + end + if m.checkSearchLevel(status) then + return + end + local obj + if parent.type == 'local' + or parent.type == 'setglobal' + or parent.type == 'setlocal' then + obj = parent + elseif parent.type == 'setfield' then + obj = parent.field + elseif parent.type == 'setmethod' then + obj = parent.method + end + if not obj then + return + end + status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1 + local newStatus = m.status(status) + m.searchRefs(newStatus, obj, 'ref') + for _, res in ipairs(newStatus.results) do + pushQueue(res, start, true) + end + status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1 +end + +local function getTableAndIndexIfIsForPairsKeyOrValue(ref) + if ref.type ~= 'local' then + return + end + + if not ref.parent or ref.parent.type ~= 'in' then + return + end + + if not ref.value or ref.value.type ~= 'select' then + return + end + + local rootSelectObj = ref.value + if rootSelectObj.index ~= 1 and rootSelectObj.index ~= 2 then + return + end + + if not rootSelectObj.vararg or rootSelectObj.vararg.type ~= 'call' then + return + end + local rootCallObj = rootSelectObj.vararg + + if not rootCallObj.node or rootCallObj.node.type ~= 'call' then + return + end + local pairsCallObj = rootCallObj.node + + if not pairsCallObj.node + or (pairsCallObj.node.special ~= 'pairs' and pairsCallObj.node.special ~= 'ipairs') then + return + end + + if not pairsCallObj.args or not pairsCallObj.args[1] then + return + end + local tableObj = pairsCallObj.args[1] + + return tableObj, rootSelectObj.index +end + +function m.checkSameSimpleAsKeyOrValueInForParis(status, ref, start, pushQueue) + local tableObj, index = getTableAndIndexIfIsForPairsKeyOrValue(ref) + if not tableObj then + return + end + + local newStatus = m.status(status) + m.searchRefs(newStatus, tableObj, 'def') + for _, def in ipairs(newStatus.results) do + if def.bindDocs then + for _, binddoc in ipairs(def.bindDocs) do + if binddoc.type == 'doc.type' then + if binddoc.types[1] and binddoc.types[1].type == 'doc.type.table' then + if index == 1 then + pushQueue(binddoc.types[1].key, start, true) + elseif index == 2 then + pushQueue(binddoc.types[1].value, start, true) + end + end + end + end + end + end +end + +--- +---@param func parser.guide.object +---@param argIndex integer +---@return integer? +local function findGenericFromArgIndexToReturnIndex(func, argIndex) + if not func.bindDocs then + return nil + end + local paramType + for _, doc in ipairs(func.bindDocs) do + if doc.type == 'doc.param' then + if doc.extends and doc.extends.paramIndex == argIndex then + paramType = doc.extends + break + end + end + end + if not paramType then + return nil + end + for _, typeUnit in ipairs(paramType.types) do + if typeUnit.typeGeneric then + local generic = typeUnit.typeGeneric[typeUnit[1]] + if generic then + for _, typeName in ipairs(generic) do + local docType = typeName.parent + if docType.returnIndex then + return docType.returnIndex + end + end + end + end + end + return nil +end + +function m.checkSameSimpleAsCallArg(status, ref, start, pushQueue) + if not status.deep then + return + end + local call, index = m.getCallAndArgIndex(ref) + if not call then + return + end + if call.parent.type ~= 'select' then + return + end + if (status.share.inSetValue or 0) > 0 then + return + end + if status.share.inBeSetValue and status.share.inBeSetValue > 5 then + return + end + status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1 + local newStatus = m.status(status) + m.searchRefs(newStatus, call.node, 'def') + for _, func in ipairs(newStatus.results) do + local rindex = findGenericFromArgIndexToReturnIndex(func, index) + if rindex then + if rindex == 1 then + pushQueue(call.parent.parent, start, true) + else + if call.extParent then + for _, slt in ipairs(call.extParent) do + if slt.index == rindex then + pushQueue(slt.parent, start, true) + end + end + end + end + end + end + status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1 +end + +local function hasTypeName(doc, name) + if doc.type == 'doc.type' then + for _, tunit in ipairs(doc.types) do + if tunit.type == 'doc.type.name' + and tunit[1] == name then + return true + end + end + end + if doc.type == 'doc.type.name' + or doc.type == 'doc.class.name' then + if doc[1] == name then + return true + end + end + return false +end + +function m.checkSameSimpleInString(status, ref, start, pushQueue, mode) + -- 特殊处理 ('xxx').xxx 的形式 + if ref.type ~= 'string' + and not hasTypeName(ref, 'string') then + return + end + if not status.interface.docType then + return + end + if status.share.searchingBindedDoc then + return + end + if not status.share.markString then + status.share.markString = {} + end + local marked = ref + if ref.type == 'string' then + marked = 'string' + end + if status.share.markString[marked] then + return + end + status.share.markString[marked] = true + local newStatus = m.status(status) + local docs = status.interface.docType('stringlib') + local mark = {} + for i = 1, #docs do + local doc = docs[i] + m.searchFields(newStatus, doc) + end + for _, res in ipairs(newStatus.results) do + if mark[res] then + goto CONTINUE + end + mark[res] = true + pushQueue(res, start + 1) + ::CONTINUE:: + end + status.share.markString[marked] = nil + return true +end + +function m.pushResult(status, mode, ref, simple) + local results = status.results + if mode == 'def' then + if ref.type == 'setglobal' + or ref.type == 'setlocal' + or ref.type == 'local' then + results[#results+1] = ref + elseif ref.type == 'setfield' + or ref.type == 'tablefield' then + results[#results+1] = ref + elseif ref.type == 'setmethod' then + results[#results+1] = ref + elseif ref.type == 'setindex' + or ref.type == 'tableindex' then + results[#results+1] = ref + elseif ref.type == 'call' then + if ref.node.special == 'rawset' then + results[#results+1] = ref + end + elseif ref.type == 'function' then + results[#results+1] = ref + elseif ref.type == 'table' then + results[#results+1] = ref + elseif ref.type == 'doc.type.function' + or ref.type == 'doc.class.name' + or ref.type == 'doc.alias.name' + or ref.type == 'doc.field' + or ref.type == 'doc.type.table' + or ref.type == 'doc.type.array' then + results[#results+1] = ref + elseif ref.type == 'doc.type' then + if #ref.enums > 0 or #ref.resumes > 0 then + results[#results+1] = ref + end + end + if ref.parent and ref.parent.type == 'return' then + if m.getParentFunction(ref) ~= m.getParentFunction(simple.node) then + results[#results+1] = ref + end + end + if m.isLiteral(ref) + and ref.parent and ref.parent.type == 'callargs' + and ref ~= simple.node then + results[#results+1] = ref + end + elseif mode == 'ref' then + if ref.type == 'setfield' + or ref.type == 'getfield' + or ref.type == 'tablefield' then + results[#results+1] = ref + elseif ref.type == 'setmethod' + or ref.type == 'getmethod' then + results[#results+1] = ref + elseif ref.type == 'setindex' + or ref.type == 'getindex' + or ref.type == 'tableindex' then + results[#results+1] = ref + elseif ref.type == 'setglobal' + or ref.type == 'getglobal' + or ref.type == 'local' + or ref.type == 'setlocal' + or ref.type == 'getlocal' then + results[#results+1] = ref + elseif ref.type == 'function' then + results[#results+1] = ref + elseif ref.type == 'table' then + results[#results+1] = ref + elseif ref.type == 'call' then + if ref.node.special == 'rawset' + or ref.node.special == 'rawget' then + results[#results+1] = ref + end + elseif ref.type == 'doc.type.function' + or ref.type == 'doc.class.name' + or ref.type == 'doc.alias.name' + or ref.type == 'doc.field' + or ref.type == 'doc.type.table' + or ref.type == 'doc.type.array' then + results[#results+1] = ref + elseif ref.type == 'doc.type' then + if #ref.enums > 0 or #ref.resumes > 0 then + results[#results+1] = ref + end + end + if ref.parent and ref.parent.type == 'return' then + results[#results+1] = ref + end + if m.isLiteral(ref) + and ref.parent + and ref.parent.type == 'callargs' + and ref ~= simple.node then + results[#results+1] = ref + end + elseif mode == 'field' then + if ref.type == 'setfield' + or ref.type == 'getfield' + or ref.type == 'tablefield' then + results[#results+1] = ref + elseif ref.type == 'setmethod' + or ref.type == 'getmethod' then + results[#results+1] = ref + elseif ref.type == 'setindex' + or ref.type == 'tableindex' then + results[#results+1] = ref + elseif ref.type == 'getindex' then + -- do not trust `t[1]` + if ref.index and ref.index.type == 'string' then + results[#results+1] = ref + end + elseif ref.type == 'setglobal' + or ref.type == 'getglobal' then + results[#results+1] = ref + elseif ref.type == 'call' then + if ref.node.special == 'rawset' + or ref.node.special == 'rawget' then + results[#results+1] = ref + end + elseif ref.type == 'doc.field' then + results[#results+1] = ref + end + elseif mode == 'deffield' then + if ref.type == 'setfield' + or ref.type == 'tablefield' then + results[#results+1] = ref + elseif ref.type == 'setmethod' then + results[#results+1] = ref + elseif ref.type == 'setindex' + or ref.type == 'tableindex' then + results[#results+1] = ref + elseif ref.type == 'setglobal' then + results[#results+1] = ref + elseif ref.type == 'call' then + if ref.node.special == 'rawset' then + results[#results+1] = ref + end + elseif ref.type == 'doc.field' then + results[#results+1] = ref + end + end +end + +function m.checkSameSimpleName(ref, sm) + if sm == m.ANY then + return true + end + + if m.getSimpleName(ref) == sm then + return true + end + if ref.type == 'doc.type' + and ref.array == true then + return true + end + return false +end + +function m.isValidSetRef(ref) + if m.isSet(ref) then + return true + end +end + +function m.checkSameSimple(status, simple, ref, start, force, mode, pushQueue) + if start > #simple then + return + end + for i = start, #simple do + local sm = simple[i] + if not force and not m.checkSameSimpleName(ref, sm) then + return + end + force = false + local cmode = mode + local skipSearch + if i < #simple then + cmode = 'ref' + else + if mode == 'deffield' then + if not m.isSet(ref) then + skipSearch = true + end + end + end + -- 检查 doc + local skipInfer = m.checkSameSimpleByBindDocs(status, ref, i, pushQueue, cmode) + or m.checkSameSimpleByDoc(status, ref, i, pushQueue, cmode) + -- 检查自己是字符串的分支情况 + m.checkSameSimpleInString(status, ref, i, pushQueue, cmode) + if not skipInfer and not skipSearch then + -- 穿透 self:func 与 mt:func + m.searchSameFieldsCrossMethod(status, ref, i, pushQueue, cmode) + -- 穿透赋值 + m.searchSameFieldsInValue(status, ref, i, pushQueue, cmode) + -- 检查自己是字面量表的情况 + m.checkSameSimpleInValueOfTable(status, ref, i, pushQueue) + -- 检查自己作为 setmetatable 第一个参数的情况 + m.checkSameSimpleInArg1OfSetMetaTable(status, ref, i, pushQueue) + -- 检查自己作为 setmetatable 调用的情况 + m.checkSameSimpleInValueOfCallMetaTable(status, ref, i, pushQueue) + -- 检查自己是特殊变量的分支的情况 + m.checkSameSimpleInSpecialBranch(status, ref, i, pushQueue) + -- self 的特殊处理 + m.checkSameSimpleInParamSelf(status, ref, i, pushQueue) + -- 自己是 call 的情况 + m.checkSameSimpleInCall(status, ref, i, pushQueue, cmode) + if cmode == 'ref' then + -- 检查形如 { a = f } 的情况 + m.checkSameSimpleAsTableField(status, ref, i, pushQueue) + -- 检查形如 return m 的情况 + m.checkSameSimpleAsReturn(status, ref, i, pushQueue) + -- 检查形如 a = f 的情况 + m.checkSameSimpleAsSetValue(status, ref, i, pushQueue) + -- 检查形如 for k,v in pairs()/ipairs() do end 的情况 + m.checkSameSimpleAsKeyOrValueInForParis(status, ref, i, pushQueue) + -- 检查自己是函数参数的情况(泛型) local x = call(V) + m.checkSameSimpleAsCallArg(status, ref, i, pushQueue) + end + end + if i == #simple then + break + end + ref = m.getNextRef(ref) + if not ref then + return + end + end + m.pushResult(status, mode, ref, simple) + local value = m.getObjectValue(ref) + if value then + m.pushResult(status, mode, value, simple) + end +end + +local queuesPool = {} +local startsPool = {} +local forcesPool = {} +local poolSize = 0 + +local function allocQueue() + if poolSize <= 0 then + return {}, {}, {} + else + local queues = queuesPool[poolSize] + local starts = startsPool[poolSize] + local forces = forcesPool[poolSize] + poolSize = poolSize - 1 + return queues, starts, forces + end +end + +local function deallocQueue(queues, starts, forces) + poolSize = poolSize + 1 + queuesPool[poolSize] = queues + startsPool[poolSize] = starts + forcesPool[poolSize] = forces +end + +function m.searchSameFields(status, simple, mode) + local queues, starts, forces = allocQueue() + local queueLen = 0 + local locks = {} + local function appendQueue(obj, start, force) + local lock = locks[start] + if not lock then + lock = {} + locks[start] = lock + end + if lock[obj] then + return + end + lock[obj] = true + queueLen = queueLen + 1 + queues[queueLen] = obj + starts[queueLen] = start + forces[queueLen] = force + if obj.mirror then + if not lock[obj.mirror] then + lock[obj.mirror] = true + queueLen = queueLen + 1 + queues[queueLen] = obj.mirror + starts[queueLen] = start + forces[queueLen] = force + end + end + end + local function pushQueue(obj, start, force) + if obj.type == 'getlocal' + or obj.type == 'setlocal' then + obj = obj.node + end + appendQueue(obj, start, force) + if obj.type == 'local' and obj.ref then + for _, ref in ipairs(obj.ref) do + appendQueue(ref, start, force) + end + end + if m.isGlobal(obj) then + local refs = m.checkSameSimpleInGlobal(status, obj) + if refs then + for _, ref in ipairs(refs) do + appendQueue(ref, start, force) + end + end + end + end + if simple.mode == 'global' then + -- 全局变量开头 + pushQueue(simple.node, 1) + elseif simple.mode == 'local' then + -- 局部变量开头 + pushQueue(simple.node, 1) + else + pushQueue(simple.node, 1) + end + local max = 0 + for i = 1, 1e6 do + if queueLen <= 0 then + break + end + local obj = queues[queueLen] + local start = starts[queueLen] + local force = forces[queueLen] + queues[queueLen] = nil + starts[queueLen] = nil + forces[queueLen] = nil + queueLen = queueLen - 1 + max = max + 1 + status.share.count = status.share.count + 1 + if status.share.count % 10000 == 0 then + --if TEST then + -- print('####', status.share.count, osClock() - status.clock) + --end + if status.interface and status.interface.pulse then + status.interface.pulse() + end + end + --if status.share.count >= 100000 then + -- logWarn('Count too large!') + -- break + --end + m.checkSameSimple(status, simple, obj, start, force, mode, pushQueue) + if max >= 10000 then + logWarn('Queue too large!') + break + end + end + --deallocQueue(queues, starts, forces) +end + +function m.getCallerInSameFile(status, func) + -- 搜索所有所在函数的调用者 + local funcRefs = m.status(status) + m.searchRefOfValue(funcRefs, func) + + local calls = {} + if #funcRefs.results == 0 then + return calls + end + for _, res in ipairs(funcRefs.results) do + local call = res.parent + if call.type == 'call' then + calls[#calls+1] = call + end + end + return calls +end + +function m.getCallerCrossFiles(status, main) + if status.interface.link then + return status.interface.link(main.uri) + end + return {} +end + +function m.searchRefsAsFunctionReturn(status, obj, mode) + if not status.deep then + return + end + if mode == 'def' then + return + end + if m.checkReturnMark(status, obj, true) then + return + end + status.results[#status.results+1] = obj + -- 搜索所在函数 + local currentFunc = m.getParentFunction(obj) + local rtn = obj.parent + if rtn.type ~= 'return' then + return + end + -- 看看他是第几个返回值 + local index + for i = 1, #rtn do + if obj == rtn[i] then + index = i + break + end + end + if not index then + return + end + local calls + if currentFunc.type == 'main' then + calls = m.getCallerCrossFiles(status, currentFunc) + else + calls = m.getCallerInSameFile(status, currentFunc) + end + -- 搜索调用者的返回值 + if #calls == 0 then + return + end + local selects = {} + for i = 1, #calls do + local parent = calls[i].parent + if parent.type == 'select' and parent.index == index then + selects[#selects+1] = parent.parent + end + local extParent = calls[i].extParent + if extParent then + for j = 1, #extParent do + local ext = extParent[j] + if ext.type == 'select' and ext.index == index then + selects[#selects+1] = ext.parent + end + end + end + end + -- 搜索调用者的引用 + for i = 1, #selects do + m.searchRefs(status, selects[i], 'ref') + end +end + +function m.searchRefsAsFunctionSet(status, obj, mode) + local parent = obj.parent + if not parent then + return + end + if parent.type == 'local' + or parent.type == 'setlocal' + or parent.type == 'setglobal' + or parent.type == 'setfield' + or parent.type == 'setmethod' + or parent.type == 'tablefield' then + m.searchRefs(status, parent, mode) + elseif parent.type == 'setindex' + or parent.type == 'tableindex' then + if parent.index == obj then + m.searchRefs(status, parent, mode) + end + end +end + +function m.searchRefsAsFunction(status, obj, mode) + if obj.type ~= 'function' + and obj.type ~= 'table' then + return + end + m.searchRefsAsFunctionSet(status, obj, mode) + -- 检查自己作为返回函数时的引用 + m.searchRefsAsFunctionReturn(status, obj, mode) +end + +function m.cleanResults(results) + local mark = {} + for i = #results, 1, -1 do + local res = results[i] + if res.tag == 'self' + or mark[res] then + results[i] = results[#results] + results[#results] = nil + else + mark[res] = true + end + end +end + +--function m.getRefCache(status, obj, mode) +-- local cache = status.interface.cache and status.interface.cache() +-- if not cache then +-- return +-- end +-- if m.isGlobal(obj) then +-- obj = m.getKeyName(obj) +-- end +-- if not cache[mode] then +-- cache[mode] = {} +-- end +-- local sourceCache = cache[mode][obj] +-- if sourceCache then +-- return sourceCache +-- end +-- sourceCache = {} +-- cache[mode][obj] = sourceCache +-- return nil, function (results) +-- for i = 1, #results do +-- sourceCache[i] = results[i] +-- end +-- end +--end + +function m.getRefCache(status, obj, mode) + local isDeep = status.deep + if mode == 'infer' then + if not isDeep then + return nil, nil + end + end + local globalCache = status.interface.cache and status.interface.cache() or {} + if m.isGlobal(obj) then + obj = m.getKeyName(obj) + end + if not obj then + return {} + end + if not globalCache[mode] then + globalCache[mode] = {} + end + local sourceCache = globalCache[mode][obj] + if sourceCache then + return sourceCache + end + if not status.share.cacheLock[mode] then + status.share.cacheLock[mode] = {} + end + if status.share.cacheLock[mode][obj] then + return {} + end + status.share.cacheLock[mode][obj] = {} + return nil, function (results) + sourceCache = {} + for i = 1, #results do + sourceCache[i] = results[i] + end + globalCache[mode][obj] = sourceCache + if not isDeep then + return + end + if mode == 'ref' + or mode == 'def' then + for i = 1, #results do + local res = results[i] + if not globalCache[mode][res] then + globalCache[mode][res] = sourceCache + end + end + end + end +end + +function m.searchRefs(status, obj, mode) + if not obj then + return + end + local cache, makeCache = m.getRefCache(status, obj, mode) + if cache then + for i = 1, #cache do + status.results[#status.results+1] = cache[i] + end + return + end + + -- 检查单步引用 + tracy.ZoneBeginN('searchRefs getStepRef') + local res = m.getStepRef(status, obj, mode) + if res then + for i = 1, #res do + status.results[#status.results+1] = res[i] + end + end + tracy.ZoneEnd() + -- 检查simple + tracy.ZoneBeginN('searchRefs searchSameFields') + if m.checkStatusDepth(status) then + local simple = m.getSimple(obj) + if simple then + m.searchSameFields(status, simple, mode) + end + end + tracy.ZoneEnd() + + m.cleanResults(status.results) + + if makeCache then + makeCache(status.results) + end +end + +function m.searchRefOfValue(status, obj) + local var = obj.parent + if var.type == 'local' + or var.type == 'set' then + return m.searchRefs(status, var, 'ref') + end +end + +function m.allocInfer(o) + if type(o.type) == 'table' then + local infers = {} + for i = 1, #o.type do + infers[i] = { + type = o.type[i], + value = o.value, + source = o.source, + level = o.level + } + end + return infers + else + return { + [1] = o, + } + end +end + +function m.mergeTypes(types) + local hasAny = types['any'] + + types['any'] = nil + + if not next(types) then + return 'any' + end + -- 同时包含 number 与 integer 时,去掉 integer + if types['number'] and types['integer'] then + types['integer'] = nil + end + + local results = {} + for tp in pairs(types) do + results[#results+1] = tp + end + -- 只有显性的 nil 与 any 时,取 any + if #results == 1 then + if results[1] == 'nil' and hasAny then + return 'any' + else + return results[1] + end + end + + tableSort(results, function (a, b) + local sa = TypeSort[a] or 100 + local sb = TypeSort[b] or 100 + if sa == sb then + return a < b + else + return sa < sb + end + end) + + return tableConcat(results, '|') +end + +function m.getClassExtends(class) + if class.type == 'doc.class.name' then + class = class.parent + end + if not class.extends then + return nil + end + local names = {} + for _, ext in ipairs(class.extends) do + names[#names+1] = ext[1] + end + return names +end + +function m.viewInferType(infers) + if not infers then + return 'any' + end + local types = {} + local hasDoc + local hasDocTable + for i = 1, #infers do + local infer = infers[i] + local src = infer.source + if src.type == 'doc.class' + or src.type == 'doc.class.name' + or src.type == 'doc.type.name' + or src.type == 'doc.type.array' + or src.type == 'doc.type.table' then + if infer.type ~= 'any' then + hasDoc = true + end + if src.type == 'doc.type.array' + or src.type == 'doc.type.table' then + hasDocTable = true + end + end + end + if hasDoc then + for i = 1, #infers do + local infer = infers[i] + local src = infer.source + if src.type == 'doc.class' + or src.type == 'doc.class.name' + or src.type == 'doc.type.name' + or src.type == 'doc.type.array' + or src.type == 'doc.type.table' + or src.type == 'doc.type.enum' + or src.type == 'doc.resume' then + local tp = infer.type or 'any' + if hasDocTable and tp == 'table' then + goto CONTINUE + end + if types[tp] == nil then + types[tp] = true + end + end + if src.type == 'doc.class' + or src.type == 'doc.class.name' then + local extends = m.getClassExtends(src) + if extends then + for _, tp in ipairs(extends) do + types[tp] = false + end + end + end + ::CONTINUE:: + end + for k, v in pairs(types) do + if not v then + types[k] = nil + end + end + else + for i = 1, #infers do + local infer = infers[i] + if infer.source.typeGeneric then + goto CONTINUE + end + local tp = infer.type or 'any' + types[tp] = true + ::CONTINUE:: + end + end + return m.mergeTypes(types) +end + +function m.checkTrue(status, source) + local newStatus = m.status(status) + m.searchInfer(newStatus, source) + -- 当前认为的结果 + local current + for _, infer in ipairs(newStatus.results) do + -- 新的结果 + local new + if infer.type == 'nil' then + new = false + elseif infer.type == 'boolean' then + if infer.value == true then + new = true + elseif infer.value == false then + new = false + end + end + if new ~= nil then + if current == nil then + current = new + else + -- 如果2个结果完全相反,则返回 nil 表示不确定 + if new ~= current then + return nil + end + end + end + end + return current +end + +--- 获取特定类型的字面量值 +function m.getInferLiteral(status, source, type) + local newStatus = m.status(status) + m.searchInfer(newStatus, source) + for _, infer in ipairs(newStatus.results) do + if infer.value ~= nil then + if type == nil or infer.type == type then + return infer.value + end + end + end + return nil +end + +--- 是否包含某种类型 +function m.hasType(status, source, type) + m.searchInfer(status, source) + for _, infer in ipairs(status.results) do + if infer.type == type then + return true + end + end + return false +end + +function m.isSameValue(status, a, b) + local statusA = m.status(status) + m.searchInfer(statusA, a) + local statusB = m.status(status) + m.searchInfer(statusB, b) + local infers = {} + for _, infer in ipairs(statusA.results) do + local literal = infer.value + if literal then + infers[literal] = false + end + end + for _, infer in ipairs(statusB.results) do + local literal = infer.value + if literal then + if infers[literal] == nil then + return false + end + infers[literal] = true + end + end + for k, v in pairs(infers) do + if v == false then + return false + end + end + return true +end + +function m.inferCheckLiteralTableWithDocVararg(status, source) + if #source ~= 1 then + return + end + local vararg = source[1] + if vararg.type ~= 'varargs' then + return + end + local results = m.getVarargDocType(status, source) + status.results[#status.results+1] = { + type = m.viewInferType(results) .. '[]', + source = source, + level = 100, + } + return true +end + +function m.inferCheckLiteral(status, source) + if source.type == 'string' then + status.results = m.allocInfer { + type = 'string', + value = source[1], + source = source, + level = 100, + } + return true + elseif source.type == 'nil' then + status.results = m.allocInfer { + type = 'nil', + value = NIL, + source = source, + level = 100, + } + return true + elseif source.type == 'boolean' then + status.results = m.allocInfer { + type = 'boolean', + value = source[1], + source = source, + level = 100, + } + return true + elseif source.type == 'number' then + if mathType(source[1]) == 'integer' then + status.results = m.allocInfer { + type = 'integer', + value = source[1], + source = source, + level = 100, + } + return true + else + status.results = m.allocInfer { + type = 'number', + value = source[1], + source = source, + level = 100, + } + return true + end + elseif source.type == 'integer' then + status.results = m.allocInfer { + type = 'integer', + source = source, + level = 100, + } + return true + elseif source.type == 'table' then + if m.inferCheckLiteralTableWithDocVararg(status, source) then + return true + end + status.results = m.allocInfer { + type = 'table', + source = source, + level = 100, + } + return true + elseif source.type == 'function' then + status.results = m.allocInfer { + type = 'function', + source = source, + level = 100, + } + return true + elseif source.type == '...' then + status.results = m.allocInfer { + type = '...', + source = source, + level = 100, + } + return true + end +end + +local function getDocAliasExtends(status, typeUnit) + if not status.interface.docType then + return nil + end + if typeUnit.type ~= 'doc.type.name' then + return nil + end + for _, doc in ipairs(status.interface.docType(typeUnit[1])) do + if doc.type == 'doc.alias.name' then + return doc.parent.extends + end + end + return nil +end + +function m.getDocTypeUnitName(status, unit) + local typeName + if unit.type == 'doc.type.name' then + typeName = unit[1] + elseif unit.type == 'doc.type.function' then + typeName = 'function' + elseif unit.type == 'doc.type.array' then + typeName = m.getDocTypeUnitName(status, unit.node) .. '[]' + elseif unit.type == 'doc.type.table' then + typeName = ('%s<%s, %s>'):format( + m.getDocTypeUnitName(status, unit.node), + m.viewInferType(m.getDocTypeNames(status, unit.key)), + m.viewInferType(m.getDocTypeNames(status, unit.value)) + ) + end + if unit.typeGeneric then + typeName = ('<%s>'):format(typeName) + end + return typeName +end + +function m.getDocTypeNames(status, doc) + local results = {} + if not doc then + return results + end + for _, unit in ipairs(doc.types) do + local alias = getDocAliasExtends(status, unit) + if alias then + local aliasResults = m.getDocTypeNames(status, alias) + for _, res in ipairs(aliasResults) do + results[#results+1] = res + end + else + local typeName = m.getDocTypeUnitName(status, unit) + results[#results+1] = { + type = typeName, + source = unit, + level = 100, + } + end + end + for _, enum in ipairs(doc.enums) do + results[#results+1] = { + type = enum[1], + source = enum, + level = 100, + } + end + for _, resume in ipairs(doc.resumes) do + if not resume.additional then + results[#results+1] = { + type = resume[1], + source = resume, + level = 100, + } + end + end + return results +end + +function m.inferCheckDoc(status, source) + if source.type == 'doc.class.name' then + status.results[#status.results+1] = { + type = source[1], + source = source, + level = 100, + } + return true + end + if source.type == 'doc.class' then + status.results[#status.results+1] = { + type = source.class[1], + source = source, + level = 100, + } + return true + end + if source.type == 'doc.type' then + local results = m.getDocTypeNames(status, source) + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true + end + if source.type == 'doc.type.function' + or source.type == 'doc.type.table' + or source.type == 'doc.type.array' then + local typeName = m.getDocTypeUnitName(status, source) + status.results[#status.results+1] = { + type = typeName, + source = source, + level = 100, + } + return true + end + if source.type == 'doc.field' then + local results = m.getDocTypeNames(status, source.extends) + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true + end + if source.type == 'doc.alias.name' then + local results = m.getDocTypeNames(status, m.getDocState(source).extends) + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true + end +end + +function m.getVarargDocType(status, source) + local func = m.getParentFunction(source) + if not func then + return + end + if not func.args then + return + end + for _, arg in ipairs(func.args) do + if arg.type == '...' then + if arg.bindDocs then + for _, doc in ipairs(arg.bindDocs) do + if doc.type == 'doc.vararg' then + return m.getDocTypeNames(status, doc.vararg) + end + end + end + end + end +end + +function m.inferCheckUpDocOfVararg(status, source) + if not source.vararg then + return + end + local results = m.getVarargDocType(status, source) + if not results then + return + end + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true +end + +function m.inferCheckUpDoc(status, source) + if m.inferCheckUpDocOfVararg(status, source) then + return true + end + local parent = source.parent + if parent then + if parent.type == 'local' + or parent.type == 'setlocal' + or parent.type == 'setglobal' then + source = parent + end + if parent.type == 'setfield' + or parent.type == 'tablefield' then + if parent.field == source + or parent.value == source then + source = parent + end + end + if parent.type == 'setmethod' then + if parent.method == source + or parent.value == source then + source = parent + end + end + if parent.type == 'setindex' + or parent.type == 'tableindex' then + if parent.index == source + or parent.value == source then + source = parent + end + end + end + local binds = source.bindDocs + if not binds then + return + end + status.results = {} + for i = #binds, 1, -1 do + local doc = binds[i] + if doc.type == 'doc.class' then + status.results[#status.results+1] = { + type = doc.class[1], + source = doc, + level = 100, + } + -- ---@class Class + -- local x = { field = 1 } + -- 这种情况下,将字面量表接受为Class的定义 + if source.value and source.value.type == 'table' then + status.results[#status.results+1] = { + type = source.value.type, + source = source.value, + level = 100, + } + end + return true + elseif doc.type == 'doc.type' then + local results = m.getDocTypeNames(status, doc) + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true + elseif doc.type == 'doc.param' then + -- function (x) 的情况 + if source.type == 'local' + and m.getKeyName(source) == doc.param[1] then + if source.parent.type == 'funcargs' + or source.parent.type == 'in' + or source.parent.type == 'loop' then + local results = m.getDocTypeNames(status, doc.extends) + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true + end + end + end + end +end + +function m.inferCheckUnary(status, source) + if source.type ~= 'unary' then + return + end + local op = source.op + if op.type == 'not' then + local checkTrue = m.checkTrue(status, source[1]) + local value = nil + if checkTrue == true then + value = false + elseif checkTrue == false then + value = true + end + status.results = m.allocInfer { + type = 'boolean', + value = value, + source = source, + level = 100, + } + return true + elseif op.type == '#' then + status.results = m.allocInfer { + type = 'integer', + source = source, + level = 100, + } + return true + elseif op.type == '~' then + local l = m.getInferLiteral(status, source[1], 'integer') + status.results = m.allocInfer { + type = 'integer', + value = l and ~l or nil, + source = source, + level = 100, + } + return true + elseif op.type == '-' then + local v = m.getInferLiteral(status, source[1], 'integer') + if v then + status.results = m.allocInfer { + type = 'integer', + value = - v, + source = source, + level = 100, + } + return true + end + v = m.getInferLiteral(status, source[1], 'number') + status.results = m.allocInfer { + type = 'number', + value = v and -v or nil, + source = source, + level = 100, + } + return true + end +end + +local function mathCheck(status, a, b) + local v1 = m.getInferLiteral(status, a, 'integer') + or m.getInferLiteral(status, a, 'number') + local v2 = m.getInferLiteral(status, b, 'integer') + or m.getInferLiteral(status, a, 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) + local int = m.hasType(status, a, 'integer') + and m.hasType(status, b, 'integer') + and not m.hasType(status, a, 'number') + and not m.hasType(status, b, 'number') + return int and 'integer' or 'number', v1, v2 +end + +function m.inferCheckBinary(status, source) + if source.type ~= 'binary' then + return + end + local op = source.op + if op.type == 'and' then + local isTrue = m.checkTrue(status, source[1]) + if isTrue == true then + m.searchInfer(status, source[2]) + return true + elseif isTrue == false then + m.searchInfer(status, source[1]) + return true + else + m.searchInfer(status, source[1]) + m.searchInfer(status, source[2]) + return true + end + elseif op.type == 'or' then + local isTrue = m.checkTrue(status, source[1]) + if isTrue == true then + m.searchInfer(status, source[1]) + return true + elseif isTrue == false then + m.searchInfer(status, source[2]) + return true + else + m.searchInfer(status, source[1]) + m.searchInfer(status, source[2]) + return true + end + elseif op.type == '==' then + local value = m.isSameValue(status, source[1], source[2]) + if value ~= nil then + status.results = m.allocInfer { + type = 'boolean', + value = value, + source = source, + level = 100, + } + return true + end + --local isSame = m.isSameDef(status, source[1], source[2]) + --if isSame == true then + -- value = true + --else + -- value = nil + --end + status.results = m.allocInfer { + type = 'boolean', + value = value, + source = source, + level = 100, + } + return true + elseif op.type == '~=' then + local value = m.isSameValue(status, source[1], source[2]) + if value ~= nil then + status.results = m.allocInfer { + type = 'boolean', + value = not value, + source = source, + level = 100, + } + return true + end + --local isSame = m.isSameDef(status, source[1], source[2]) + --if isSame == true then + -- value = false + --else + -- value = nil + --end + status.results = m.allocInfer { + type = 'boolean', + value = value, + source = source, + level = 100, + } + return true + elseif op.type == '<=' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + or m.getInferLiteral(status, source[1], 'number') + local v2 = m.getInferLiteral(status, source[2], 'integer') + or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) + local v + if v1 and v2 then + v = v1 <= v2 + end + status.results = m.allocInfer { + type = 'boolean', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '>=' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + or m.getInferLiteral(status, source[1], 'number') + local v2 = m.getInferLiteral(status, source[2], 'integer') + or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) + local v + if v1 and v2 then + v = v1 >= v2 + end + status.results = m.allocInfer { + type = 'boolean', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '<' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + or m.getInferLiteral(status, source[1], 'number') + local v2 = m.getInferLiteral(status, source[2], 'integer') + or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) + local v + if v1 and v2 then + v = v1 < v2 + end + status.results = m.allocInfer { + type = 'boolean', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '>' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + or m.getInferLiteral(status, source[1], 'number') + local v2 = m.getInferLiteral(status, source[2], 'integer') + or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) + local v + if v1 and v2 then + v = v1 > v2 + end + status.results = m.allocInfer { + type = 'boolean', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '|' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) + local v + if v1 and v2 then + v = v1 | v2 + end + status.results = m.allocInfer { + type = 'integer', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '~' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) + local v + if v1 and v2 then + v = v1 ~ v2 + end + status.results = m.allocInfer { + type = 'integer', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '&' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) + local v + if v1 and v2 then + v = v1 & v2 + end + status.results = m.allocInfer { + type = 'integer', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '<<' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) + local v + if v1 and v2 then + v = v1 << v2 + end + status.results = m.allocInfer { + type = 'integer', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '>>' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) + local v + if v1 and v2 then + v = v1 >> v2 + end + status.results = m.allocInfer { + type = 'integer', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '..' then + local v1 = m.getInferLiteral(status, source[1], 'string') + local v2 = m.getInferLiteral(status, source[2], 'string') + v1 = type(v1) == 'string' and v1 or nil + v2 = type(v2) == 'string' and v2 or nil + local v + if v1 and v2 then + v = v1 .. v2 + end + status.results = m.allocInfer { + type = 'string', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '^' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + or m.getInferLiteral(status, source[1], 'number') + local v2 = m.getInferLiteral(status, source[2], 'integer') + or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) + local v + if v1 and v2 then + v = v1 ^ v2 + end + status.results = m.allocInfer { + type = 'number', + value = v, + source = source, + level = 100, + } + return true + elseif op.type == '/' then + local v1 = m.getInferLiteral(status, source[1], 'integer') + or m.getInferLiteral(status, source[1], 'number') + local v2 = m.getInferLiteral(status, source[2], 'integer') + or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) + local v + if v1 and v2 and v2 ~= 0 then + v = v1 / v2 + end + status.results = m.allocInfer { + type = 'number', + value = v, + source = source, + level = 100, + } + return true + -- 其他数学运算根据2侧的值决定,当2侧的值均为整数时返回整数 + elseif op.type == '+' then + local int, v1, v2 = mathCheck(status, source[1], source[2]) + status.results = m.allocInfer{ + type = int, + value = (v1 and v2) and (v1 + v2) or nil, + source = source, + level = 100, + } + return true + elseif op.type == '-' then + local int, v1, v2 = mathCheck(status, source[1], source[2]) + status.results = m.allocInfer{ + type = int, + value = (v1 and v2) and (v1 - v2) or nil, + source = source, + level = 100, + } + return true + elseif op.type == '*' then + local int, v1, v2 = mathCheck(status, source[1], source[2]) + status.results = m.allocInfer { + type = int, + value = (v1 and v2) and (v1 * v2) or nil, + source = source, + level = 100, + } + return true + elseif op.type == '%' then + local int, v1, v2 = mathCheck(status, source[1], source[2]) + status.results = m.allocInfer { + type = int, + value = (v1 and v2 and v2 ~= 0) and (v1 % v2) or nil, + source = source, + level = 100, + } + return true + elseif op.type == '//' then + local int, v1, v2 = mathCheck(status, source[1], source[2]) + status.results = m.allocInfer { + type = int, + value = (v1 and v2 and v2 ~= 0) and (v1 // v2) or nil, + source = source, + level = 100, + } + return true + end +end + +function m.inferByDef(status, obj) + if not status.share.inferedDef then + status.share.inferedDef = {} + end + if status.share.inferedDef[obj] then + return + end + status.share.inferedDef[obj] = true + local mark = {} + local newStatus = m.status(status, status.interface) + tracy.ZoneBeginN('inferByDef searchRefs') + m.searchRefs(newStatus, obj, 'def') + tracy.ZoneEnd() + for _, src in ipairs(newStatus.results) do + local inferStatus = m.status(newStatus) + m.searchInfer(inferStatus, src) + if #inferStatus.results == 0 then + status.results[#status.results+1] = { + type = 'any', + source = src, + level = 0, + } + else + for _, infer in ipairs(inferStatus.results) do + if not mark[infer.source] then + mark[infer.source] = true + status.results[#status.results+1] = infer + end + end + end + end +end + +local function inferBySetOfLocal(status, source) + if status.share[source] then + return + end + status.share[source] = true + local newStatus = m.status(status) + if source.value then + m.searchInfer(newStatus, source.value) + end + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + break + end + m.searchInfer(newStatus, ref) + end + for _, infer in ipairs(newStatus.results) do + status.results[#status.results+1] = infer + end + end +end + +function m.inferByLocalRef(status, source) + if #status.results ~= 0 then + return + end + if source.type == 'local' then + inferBySetOfLocal(status, source) + elseif source.type == 'setlocal' + or source.type == 'getlocal' then + inferBySetOfLocal(status, source.node) + end +end + +function m.inferByCall(status, source) + if not source.parent then + return + end + if source.parent.type ~= 'call' then + return + end + if source.parent.node == source then + status.results[#status.results+1] = { + type = 'function', + source = source, + level = 10, + } + return + end +end + +function m.inferByGetTable(status, source) + if source.type == 'field' + or source.type == 'method' then + source = source.parent + end + local next = source.next + if not next then + return + end + if next.type == 'getfield' + or next.type == 'getindex' + or next.type == 'setmethod' + or next.type == 'setfield' + or next.type == 'setindex' then + status.results[#status.results+1] = { + type = 'table', + source = source, + level = 10, + } + elseif next.type == 'getmethod' then + status.results[#status.results+1] = { + type = 'table', + source = source, + level = 5, + } + status.results[#status.results+1] = { + type = 'string', + source = source, + level = 5, + } + end +end + +function m.inferByUnary(status, source) + local parent = source.parent + if not parent or parent.type ~= 'unary' then + return + end + local op = parent.op + if op.type == '#' then + status.results[#status.results+1] = { + type = 'string', + source = source, + level = 5, + } + status.results[#status.results+1] = { + type = 'table', + source = source, + level = 5, + } + elseif op.type == '~' then + status.results[#status.results+1] = { + type = 'integer', + source = source, + level = 10, + } + elseif op.type == '-' then + status.results[#status.results+1] = { + type = 'number', + source = source, + level = 10, + } + end +end + +function m.inferByBinary(status, source) + local parent = source.parent + if not parent or parent.type ~= 'binary' then + return + end + local op = parent.op + if op.type == '<=' + or op.type == '>=' + or op.type == '<' + or op.type == '>' + or op.type == '^' + or op.type == '/' + or op.type == '+' + or op.type == '-' + or op.type == '*' + or op.type == '%' then + status.results[#status.results+1] = { + type = 'number', + source = source, + level = 10, + } + elseif op.type == '|' + or op.type == '~' + or op.type == '&' + or op.type == '<<' + or op.type == '>>' + -- 整数的可能性比较高 + or op.type == '//' then + status.results[#status.results+1] = { + type = 'integer', + source = source, + level = 10, + } + elseif op.type == '..' then + status.results[#status.results+1] = { + type = 'string', + source = source, + level = 10, + } + end +end + +local function mergeFunctionReturnsByDoc(status, source, index, call) + if not source or source.type ~= 'function' then + return + end + if not source.bindDocs then + return + end + local returns = {} + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.return' then + for _, rtn in ipairs(doc.returns) do + returns[#returns+1] = rtn + end + end + end + local rtn = returns[index] + if not rtn then + return + end + local results = m.getDocTypeNames(status, rtn) + if #results == 0 then + return + end + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true +end + +local function mergeDocTypeFunctionReturns(status, source, index) + if not source.bindDocs then + return + end + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.type' then + for _, typeUnit in ipairs(doc.types) do + if typeUnit.type == 'doc.type.function' then + local rtn = typeUnit.returns[index] + if rtn then + local results = m.getDocTypeNames(status, rtn) + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + end + end + end + end + end +end + +local function mergeFunctionReturns(status, source, index, call) + local returns = source.returns + if not returns then + return + end + for i = 1, #returns do + local rtn = returns[i] + if rtn[index] then + if rtn[index].type == 'call' then + if not m.checkReturnMark(status, rtn[index], true) then + m.inferByCallReturnAndIndex(status, rtn[index], index) + end + else + local newStatus = m.status(status) + m.searchInfer(newStatus, rtn[index]) + if #newStatus.results == 0 then + status.results[#status.results+1] = { + type = 'any', + source = rtn[index], + level = 0, + } + else + for _, infer in ipairs(newStatus.results) do + status.results[#status.results+1] = infer + end + end + end + end + end +end + +function m.inferByCallReturnAndIndex(status, call, index) + local node = call.node + local newStatus = m.status(status, status.interface) + m.searchRefs(newStatus, node, 'def') + local hasDocReturn + for _, src in ipairs(newStatus.results) do + if mergeDocTypeFunctionReturns(status, src, index) then + hasDocReturn = true + elseif mergeFunctionReturnsByDoc(status, src.value, index, call) then + hasDocReturn = true + end + end + if not hasDocReturn then + for _, src in ipairs(newStatus.results) do + if src.value and src.value.type == 'function' then + if not m.checkReturnMark(status, src.value, true) then + mergeFunctionReturns(status, src.value, index, call) + end + end + end + end +end + +function m.inferByCallReturn(status, source) + if source.type == 'call' then + m.inferByCallReturnAndIndex(status, source, 1) + return + end + if source.type ~= 'select' then + if source.value and source.value.type == 'select' then + source = source.value + else + return + end + end + if not source.vararg or source.vararg.type ~= 'call' then + return + end + m.inferByCallReturnAndIndex(status, source.vararg, source.index) +end + +function m.inferByPCallReturn(status, source) + if source.type ~= 'select' then + if source.value and source.value.type == 'select' then + source = source.value + else + return + end + end + local call = source.vararg + if not call or call.type ~= 'call' then + return + end + if not call.args then + return + end + local node = call.node + local specialName = node.special + local func, index + if specialName == 'pcall' then + func = call.args[1] + index = source.index - 1 + elseif specialName == 'xpcall' then + func = call.args[1] + index = source.index - 2 + else + return + end + local newStatus = m.status(status, status.interface) + m.searchRefs(newStatus, func, 'def') + for _, src in ipairs(newStatus.results) do + if src.value and src.value.type == 'function' then + mergeFunctionReturns(status, src.value, index) + end + end +end + +function m.cleanInfers(infers, obj) + -- kick lower level infers + local level = 0 + if obj.type ~= 'select' then + for i = 1, #infers do + local infer = infers[i] + if infer.level > level then + level = infer.level + end + end + end + -- merge infers + local mark = {} + for i = #infers, 1, -1 do + local infer = infers[i] + if infer.level < level then + infers[i] = infers[#infers] + infers[#infers] = nil + goto CONTINUE + end + local key = ('%p'):format(infer.type, infer.source) + if mark[key] then + infers[i] = infers[#infers] + infers[#infers] = nil + else + mark[key] = true + end + ::CONTINUE:: + end + -- kick doc.generic + if #infers > 1 then + for i = #infers, 1, -1 do + local infer = infers[i] + if infer.source.typeGeneric then + infers[i] = infers[#infers] + infers[#infers] = nil + end + end + end +end + +function m.searchInfer(status, obj) + while obj.type == 'paren' do + obj = obj.exp + if not obj then + return + end + end + while true do + local value = m.getObjectValue(obj) + if not value or value == obj then + break + end + obj = value + end + if not obj then + return + end + + local cache, makeCache = m.getRefCache(status, obj, 'infer') + if cache then + for i = 1, #cache do + status.results[#status.results+1] = cache[i] + end + return + end + + if DEVELOP then + status.share.clock = status.share.clock or osClock() + end + + if not status.share.lockInfer then + status.share.lockInfer = {} + end + if status.share.lockInfer[obj] then + return + end + status.share.lockInfer[obj] = true + + local checked = m.inferCheckDoc(status, obj) + or m.inferCheckUpDoc(status, obj) + or m.inferCheckLiteral(status, obj) + or m.inferCheckUnary(status, obj) + or m.inferCheckBinary(status, obj) + if checked then + m.cleanInfers(status.results, obj) + if makeCache then + makeCache(status.results) + end + return + end + + m.inferByLocalRef(status, obj) + if status.deep then + tracy.ZoneBeginN('inferByDef') + m.inferByDef(status, obj) + tracy.ZoneEnd() + end + --m.inferByCall(status, obj) + m.inferByGetTable(status, obj) + m.inferByUnary(status, obj) + m.inferByBinary(status, obj) + m.cleanInfers(status.results, obj) + if makeCache then + makeCache(status.results) + end +end + +--- 请求对象的引用,包括 `a.b.c` 形式 +--- 与 `return function` 形式。 +--- 不穿透 `setmetatable` ,考虑由 +--- 业务层进行反向 def 搜索。 +function m.requestReference(obj, interface, deep) + local status = m.status(nil, interface, deep) + -- 根据 field 搜索引用 + m.searchRefs(status, obj, 'ref') + + m.searchRefsAsFunction(status, obj, 'ref') + + if m.debugMode then + --print('count:', status.share.count) + end + + return status.results, status.share.count +end + +--- 请求对象的定义,包括 `a.b.c` 形式 +--- 与 `return function` 形式。 +--- 穿透 `setmetatable` 。 +function m.requestDefinition(obj, interface, deep) + local status = m.status(nil, interface, deep) + -- 根据 field 搜索定义 + m.searchRefs(status, obj, 'def') + + return status.results, status.share.count +end + +--- 请求对象的字段 +---@param filterKey nil|string|table +function m.requestFields(obj, interface, deep, filterKey) + local status = m.status(nil, interface, deep) + + m.searchFields(status, obj, filterKey) + + return status.results, status.share.count +end + +--- 请求对象的定义字段 +---@param filterKey nil|string|table +function m.requestDefFields(obj, interface, deep, filterKey) + local status = m.status(nil, interface, deep) + + m.searchDefFields(status, obj, filterKey) + + return status.results, status.share.count +end + +--- 请求对象的类型推测 +function m.requestInfer(obj, interface, deep) + local status = m.status(nil, interface, deep) + m.searchInfer(status, obj) + + return status.results, status.share.count +end + +function m.debugView(obj) + return require 'files'.position(m.getUri(obj), obj.start), m.getUri(obj) +end + +return m diff --git a/script/core/linker.lua b/script/core/linker.lua index d7f53fa4..6002b22c 100644 --- a/script/core/linker.lua +++ b/script/core/linker.lua @@ -1,4 +1,5 @@ local util = require 'utility' +local guide = require 'parser.guide' local function getKey(source) if source.type == 'local' then @@ -33,6 +34,15 @@ local function checkGlobal(source) return nil end +local function checkLocal(source) + if source.type == 'local' + or source.type == 'setlocal' + or source.type == 'getlocal' then + return true + end + return nil +end + local function checkTableField(source) if source.type == 'table' then return true @@ -56,35 +66,61 @@ local function checkFunctionReturn(source) return nil end +local IDList = {} local function getID(source) if source.type == 'field' or source.type == 'method' then source = source.parent end local current = source - local idList = {} + local index = 0 while true do local id, node = getKey(current) if not id then break end - idList[#idList+1] = id + index = index + 1 + IDList[index] = id source = current if not node then break end current = node end - util.revertTable(idList) - local id = table.concat(idList, '|') + for i = index + 1, #IDList do + IDList[i] = nil + end + util.revertTable(IDList) + local id = table.concat(IDList, '|') return id, current end +---@class link +---@field id string +-- 语法树单元 +---@field source parser.guide.object +-- 是否是局部变量 +---@field loc boolean +-- 是否是全局变量 +---@field global boolean +-- 是否是字面量表中的字段 +---@field tfield boolean +-- 返回值,文件返回值总是0,函数返回值为第几个返回值 +---@field freturn integer + ---创建source的链接信息 +---@param source parser.guide.object +---@return link local function createLink(source) local id, node = getID(source) + if not id then + return nil + end return { - id = id, + id = id, + source = source, + -- 局部变量 + loc = checkLocal(node), -- 全局变量 global = checkGlobal(node), -- 字面量表中的字段 @@ -94,9 +130,31 @@ local function createLink(source) } end +local function insertLinker(linkers, tp, link) + local list = linkers[tp] + local id = link.id + if not list[id] then + list[id] = {} + end + list[id][#list[id]+1] = link + link._linker = list[id] +end + local m = {} +---根据语法树单元获取关联的link列表 +---@param source parser.guide.object +---@return link[]? +function m.getLinkersBySource(source) + if not source._link then + source._link = createLink(source) + end + return source._link and source._link._linker +end + ---获取source的链接信息 +---@param source parser.guide.object +---@return link function m.getLink(source) if not source._link then source._link = createLink(source) @@ -104,4 +162,36 @@ function m.getLink(source) return source._link end +---编译整个文件的link +---@param source parser.guide.object +---@return table +function m.compileLinks(source) + local root = guide.getRoot(source) + if root._linkers then + return root._linkers + end + local linkers = { + loc = {}, + global = {}, + tfield = {}, + } + guide.eachSource(root, function (src) + local link = m.getLink(src) + if not link then + return + end + if link.global then + insertLinker(linkers, 'global', link) + end + if link.loc then + insertLinker(linkers, 'loc', link) + end + if link.tfield then + insertLinker(linkers, 'tfield', link) + end + end) + root._linkers = linkers + return linkers +end + return m diff --git a/script/parser/guide.lua b/script/parser/guide.lua index 2369e84f..46c32f3a 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -293,10 +293,19 @@ end ---@param obj parser.guide.object ---@return parser.guide.object function m.getRoot(obj) + local source = obj + if source._root then + return source._root + end for _ = 1, 1000 do if obj.type == 'main' then + source._root = obj return obj end + if obj._root then + source._root = obj._root + return source._root + end local parent = obj.parent if not parent then return nil diff --git a/test/basic/linker.lua b/test/basic/linker.lua index bf4e50bb..0be93e8d 100644 --- a/test/basic/linker.lua +++ b/test/basic/linker.lua @@ -37,24 +37,28 @@ local function TEST(script) end CARE['id'] = true +CARE['loc'] = true TEST [[ local <?x?> ]] { - id = '9', + id = '9', + loc = true, } TEST [[ local x print(<?x?>) ]] { - id = '7', + id = '7', + loc = true, } TEST [[ local x <?x?> = 1 ]] { - id = '7', + id = '7', + loc = true, } CARE['global'] = true @@ -76,14 +80,16 @@ TEST [[ local x print(x.y.<?z?>) ]] { - id = '7|"y"|"z"', + id = '7|"y"|"z"', + loc = true, } TEST [[ local x function x:<?f?>() end ]] { - id = '7|"f"', + id = '7|"f"', + loc = true, } TEST [[ diff --git a/test/basic/linker.txt b/test/basic/linker.txt new file mode 100644 index 00000000..18176369 --- /dev/null +++ b/test/basic/linker.txt @@ -0,0 +1,16 @@ +ast -> linkers = { + globals = { + ['"X"|"Y"|"Z"'] = {src1, src2, src3}, + ['"X"|"Y"'] = {src4, src5, src6}, + ['"X"'] = {src7, src8, src9}, + }, + locals = { + ['7'] = {src10}, + ['7|"x"'] = {src11}, + }, + tfield = { + ['11|"k"'] = {src12}, + }, +} + + |