local guide = require 'parser.guide' local util = require 'utility' local config = require 'config' local rpath = require 'workspace.require-path' local files = require 'files' ---@class vm local vm = require 'vm.vm' ---@class parser.object ---@field _compiledNodes boolean ---@field _node vm.node ---@field _globalBase table ---@field cindex integer ---@field func parser.object -- 该函数有副作用,会给source绑定node! ---@param source parser.object ---@return boolean local function bindDocs(source) local docs = source.bindDocs if not docs then return false end local isParam = source.parent.type == 'funcargs' or (source.parent.type == 'in' and source.finish <= source.parent.keys.finish) for i = #docs, 1, -1 do local doc = docs[i] if doc.type == 'doc.type' then if not isParam then vm.setNode(source, vm.compileNode(doc)) return true end end if doc.type == 'doc.class' then if (source.type == 'local' and not isParam) or (source._globalNode and guide.isSet(source)) or source.type == 'tablefield' or source.type == 'tableindex' then vm.setNode(source, vm.compileNode(doc)) return true end end if doc.type == 'doc.param' then if isParam and source[1] == doc.param[1] then local node = vm.compileNode(doc) if doc.optional then node:addOptional() end vm.setNode(source, node) return true end end if doc.type == 'doc.module' then local name = doc.module if not name then return true end local uri = rpath.findUrisByRequirePath(guide.getUri(source), name)[1] if not uri then return true end local state = files.getState(uri) local ast = state and state.ast if not ast then return true end vm.setNode(source, vm.compileNode(ast)) return true end if doc.type == 'doc.overload' then if not isParam then vm.setNode(source, vm.compileNode(doc)) end end end return false end local searchFieldSwitch = util.switch() : case 'table' : call(function (suri, source, key, ref, pushResult) local hasFiled = false for _, field in ipairs(source) do if field.type == 'tablefield' or field.type == 'tableindex' then local fieldKey = guide.getKeyName(field) if key == nil or key == fieldKey then hasFiled = true pushResult(field) end end if field.type == 'tableexp' then if key == nil or key == field.tindex then hasFiled = true pushResult(field) end end if field.type == 'varargs' then if not hasFiled and type(key) == 'number' and key >= 1 and math.tointeger(key) then hasFiled = true pushResult(field) end if key == nil then pushResult(field) end end end end) : case 'string' : case 'doc.type.string' : call(function (suri, source, key, ref, pushResult) -- change to `string: stringlib` ? local stringlib = vm.getGlobal('type', 'stringlib') if stringlib then vm.getClassFields(suri, stringlib, key, ref, pushResult) end end) : case 'local' : case 'self' : call(function (suri, node, key, ref, pushResult) local fields if key then fields = vm.getLocalSourcesSets(node, key) else fields = vm.getLocalFields(node, false) end if not fields then return end local hasMarkDoc = {} for _, src in ipairs(fields) do if src.bindDocs then if bindDocs(src) then local skey = guide.getKeyName(src) if skey then hasMarkDoc[skey] = true end pushResult(src, true) end end end for _, src in ipairs(fields) do local skey = guide.getKeyName(src) if not hasMarkDoc[skey] then pushResult(src) end end end) : case 'doc.type.array' : call(function (suri, source, key, ref, pushResult) if type(key) == 'number' then if key < 1 or not math.tointeger(key) then return end pushResult(source.node) end if type(key) == 'table' then if vm.isSubType(suri, key, 'integer') then pushResult(source.node) end end end) : case 'doc.type.table' : call(function (suri, source, key, ref, pushResult) for _, field in ipairs(source.fields) do local fieldKey = field.name if fieldKey.type == 'doc.type' then local fieldNode = vm.compileNode(fieldKey) for fn in fieldNode:eachObject() do if fn.type == 'global' and fn.cate == 'type' then if key == nil or fn.name == 'any' or (fn.name == 'boolean' and type(key) == 'boolean') or (fn.name == 'number' and type(key) == 'number') or (fn.name == 'integer' and math.tointeger(key)) or (fn.name == 'string' and type(key) == 'string') then pushResult(field) end end end end if fieldKey.type == 'doc.field.name' then if key == nil or fieldKey[1] == key then pushResult(field) end end end end) : case 'global' : call(function (suri, node, key, ref, pushResult) if node.cate == 'variable' then if key then if type(key) ~= 'string' then return end local global = vm.getGlobal('variable', node.name, key) if global then for _, set in ipairs(global:getSets(suri)) do pushResult(set) end if ref then for _, get in ipairs(global:getGets(suri)) do pushResult(get) end end end else local globals = vm.getGlobalFields('variable', node.name) for _, global in ipairs(globals) do for _, set in ipairs(global:getSets(suri)) do pushResult(set) end if ref then for _, get in ipairs(global:getGets(suri)) do pushResult(get) end end end end end if node.cate == 'type' then vm.getClassFields(suri, node, key, ref, pushResult) end end) : default(function (suri, source, key, ref, pushResult) local node = source._globalNode if not node then return end if node.cate == 'variable' then if key then if type(key) ~= 'string' then return end local global = vm.getGlobal('variable', node.name, key) if global then for _, set in ipairs(global:getSets(suri)) do pushResult(set) end for _, get in ipairs(global:getGets(suri)) do pushResult(get) end end else local globals = vm.getGlobalFields('variable', node.name) for _, global in ipairs(globals) do for _, set in ipairs(global:getSets(suri)) do pushResult(set) end for _, get in ipairs(global:getGets(suri)) do pushResult(get) end end end end if node.cate == 'type' then vm.getClassFields(suri, node, key, ref, pushResult) end end) ---@param suri uri ---@param object vm.global ---@param key string|vm.global ---@param ref boolean ---@param pushResult fun(field: vm.object, isMark?: boolean) function vm.getClassFields(suri, object, key, ref, pushResult) local mark = {} local function searchClass(class, searchedFields) local name = class.name if mark[name] then return end mark[name] = true searchedFields = searchedFields or {} for _, set in ipairs(class:getSets(suri)) do if set.type == 'doc.class' then -- check ---@field local hasFounded = {} for _, field in ipairs(set.fields) do local fieldKey = guide.getKeyName(field) if fieldKey then -- ---@field x boolean -> class.x if key == nil or fieldKey == key then if not searchedFields[fieldKey] then pushResult(field, true) hasFounded[fieldKey] = true end end end if not hasFounded[fieldKey] then local keyType = type(key) if keyType == 'table' then -- ---@field [integer] boolean -> class[integer] local fieldNode = vm.compileNode(field.field) if vm.isSubType(suri, key.name, fieldNode) then local nkey = '|' .. key.name if not searchedFields[nkey] then pushResult(field, true) hasFounded[nkey] = true end end else local typeName if keyType == 'number' then if math.tointeger(key) then typeName = 'integer' else typeName = 'number' end elseif keyType == 'boolean' or keyType == 'string' then typeName = keyType end if typeName and field.field.type ~= 'doc.field.name' then -- ---@field [integer] boolean -> class[1] local fieldNode = vm.compileNode(field.field) if vm.isSubType(suri, typeName, fieldNode) then local nkey = '|' .. typeName if not searchedFields[nkey] then pushResult(field, true) hasFounded[nkey] = true end end end end end end -- check local field and global field if not hasFounded[key] and set.bindSources then for _, src in ipairs(set.bindSources) do local skipSetLocal if src.value and src.value.type == 'table' then searchFieldSwitch('table', suri, src.value, key, ref, function (field) local fieldKey = guide.getKeyName(field) if fieldKey then if not searchedFields[fieldKey] and guide.isSet(field) then hasFounded[fieldKey] = true pushResult(field, true) if src.type == 'local' then skipSetLocal = true end end end end) end if not skipSetLocal then searchFieldSwitch(src.type, suri, src, key, ref, function (field) local fieldKey = guide.getKeyName(field) if fieldKey then if not searchedFields[fieldKey] and guide.isSet(field) then hasFounded[fieldKey] = true pushResult(field, true) end end end) end end end -- look into extends(if field not found) if not hasFounded[key] and set.extends then for fieldKey in pairs(hasFounded) do searchedFields[fieldKey] = true end for _, extend in ipairs(set.extends) do if extend.type == 'doc.extends.name' then local extendType = vm.getGlobal('type', extend[1]) if extendType then searchClass(extendType, searchedFields) end end end end end end end local function searchGlobal(class) if class.cate == 'type' and class.name == '_G' then if key == nil then local sets = vm.getGlobalSets(suri, 'variable') for _, set in ipairs(sets) do pushResult(set) end elseif type(key) == 'string' then local global = vm.getGlobal('variable', key) if global then for _, set in ipairs(global:getSets(suri)) do pushResult(set) end end end end end searchClass(object) searchGlobal(object) end ---@class parser.object ---@field _sign vm.sign|false ---@param source parser.object ---@return vm.sign|false local function getObjectSign(source) if source._sign ~= nil then return source._sign end source._sign = false if source.type == 'function' then if not source.bindDocs then return false end for _, doc in ipairs(source.bindDocs) do if doc.type == 'doc.generic' then if not source._sign then source._sign = vm.createSign() break end end end if not source._sign then return false end if source.args then for _, arg in ipairs(source.args) do local argNode = vm.compileNode(arg) if arg.optional then argNode:addOptional() end source._sign:addSign(argNode) end end end if source.type == 'doc.type.function' or source.type == 'doc.type.table' or source.type == 'doc.type.array' then local hasGeneric guide.eachSourceType(source, 'doc.generic.name', function () hasGeneric = true end) if not hasGeneric then return false end source._sign = vm.createSign() if source.type == 'doc.type.function' then for _, arg in ipairs(source.args) do if arg.extends then local argNode = vm.compileNode(arg.extends) if arg.optional then argNode:addOptional() end source._sign:addSign(argNode) else source._sign:addSign(vm.createNode()) end end end end return source._sign end ---@param func parser.object ---@param index integer ---@return (parser.object|vm.generic)? function vm.getReturnOfFunction(func, index) if func.type == 'function' then if not func._returns then func._returns = {} end if not func._returns[index] then func._returns[index] = { type = 'function.return', parent = func, returnIndex = index, } end return func._returns[index] end if func.type == 'doc.type.function' then local rtn = func.returns[index] if not rtn then local lastReturn = func.returns[#func.returns] if lastReturn and lastReturn.name and lastReturn.name[1] == '...' then rtn = lastReturn else return nil end end local sign = getObjectSign(func) if not sign then return rtn end return vm.createGeneric(rtn, sign) end return nil end ---@param args parser.object[] ---@return vm.node local function getReturnOfSetMetaTable(args) local tbl = args[1] local mt = args[2] local node = vm.createNode() if tbl then node:merge(vm.compileNode(tbl)) end if mt then vm.compileByParentNode(mt, '__index', false, function (src) for n in vm.compileNode(src):eachObject() do if n.type == 'global' or n.type == 'local' or n.type == 'table' or n.type == 'doc.type.table' then node:merge(n) end end end) end return node end ---@param source parser.object local function matchCall(source) local call = source.parent if not call or call.type ~= 'call' or call.node ~= source then return end local funcs = vm.getMatchedFunctions(source, call.args) local myNode = vm.getNode(source) if not myNode then return end local needRemove for n in myNode:eachObject() do if n.type == 'function' or n.type == 'doc.type.function' then if not util.arrayHas(funcs, n) then if not needRemove then needRemove = vm.createNode() end needRemove:merge(n) end end end if needRemove then local newNode = myNode:copy() newNode:removeNode(needRemove) vm.setNode(source, newNode, true) end end ---@param func parser.object ---@param index integer ---@param args parser.object[] ---@return vm.node local function getReturn(func, index, args) if not func._callReturns then func._callReturns = {} end if not func._callReturns[index] then local call = func.parent func._callReturns[index] = { type = 'call.return', parent = call, func = func, cindex = index, args = args, start = call.start, finish = call.finish, } end return vm.compileNode(func._callReturns[index]) end ---@param source parser.object ---@return boolean local function bindAs(source) local root = guide.getRoot(source) local docs = root.docs if not docs then return false end local ases = docs._asCache if not ases then ases = {} docs._asCache = ases for _, doc in ipairs(docs) do if doc.type == 'doc.as' and doc.as and doc.touch then ases[#ases+1] = doc end end table.sort(ases, function (a, b) return a.touch < b.touch end) end if #ases == 0 then return false end local max = #ases local index local left = 1 local right = max for _ = 1, 1000 do if left == right then index = left break end index = left + (right - left) // 2 local doc = ases[index] if doc.touch < source.finish then left = index + 1 else right = index end end local doc = ases[index] if doc and doc.touch == source.finish then vm.setNode(source, vm.compileNode(doc.as), true) return true end return false end ---@param source parser.object ---@param key? string|vm.global ---@param pushResult fun(source: parser.object) function vm.compileByParentNode(source, key, ref, pushResult) local parentNode = vm.compileNode(source) local docedResults = {} local commonResults = {} local suri = guide.getUri(source) local hasClass for node in parentNode:eachObject() do if node.type == 'global' and node.cate == 'type' ---@cast node vm.global and not guide.isBasicType(node.name) then hasClass = true break end end for node in parentNode:eachObject() do if not hasClass or ( node.type == 'global' and node.cate == 'type' ---@cast node vm.global and not guide.isBasicType(node.name) ) or guide.isLiteral(node) then searchFieldSwitch(node.type, suri, node, key, ref, function (res, markDoc) if markDoc then docedResults[#docedResults+1] = res else commonResults[#commonResults+1] = res end end) end end if #docedResults > 0 then for _, res in ipairs(docedResults) do pushResult(res) end end if #docedResults == 0 or key == nil then for _, res in ipairs(commonResults) do pushResult(res) end end end ---@param list parser.object[] ---@param index integer ---@return vm.node ---@return parser.object? function vm.selectNode(list, index) local exp if list[index] then exp = list[index] index = 1 else for i = index, 1, -1 do if list[i] then local last = list[i] if last.type == 'call' or last.type == 'varargs' then index = index - i + 1 exp = last end break end end end if not exp then return vm.createNode(vm.declareGlobal('type', 'nil')), nil end ---@type vm.node? local result if exp.type == 'call' then result = getReturn(exp.node, index, exp.args) if result:isEmpty() then result:merge(vm.declareGlobal('type', 'unknown')) end else ---@type vm.node result = vm.compileNode(exp) if exp.type == 'varargs' and result:isEmpty() then result:merge(vm.declareGlobal('type', 'unknown')) end end return result, exp end ---@param source parser.object ---@param list parser.object[] ---@param index integer ---@return vm.node local function selectNode(source, list, index) local result = vm.selectNode(list, index) if source.type == 'function.return' then -- remove any for returns local rtnNode = vm.createNode() for n in result:eachObject() do if guide.isLiteral(n) then rtnNode:merge(n) end if n.type == 'global' and n.cate == 'type' then if n.name ~= 'any' then rtnNode:merge(n) end else rtnNode:merge(n) end end vm.setNode(source, rtnNode) return rtnNode end vm.setNode(source, result) return result end ---@param source parser.object ---@param node vm.node.object ---@return boolean local function isValidCallArgNode(source, node) if source.type == 'function' then return node.type == 'doc.type.function' end if source.type == 'table' then return node.type == 'doc.type.table' or node.type == 'doc.type.array' or ( node.type == 'global' and node.cate == 'type' ---@cast node vm.global and not guide.isBasicType(node.name) ) end if source.type == 'dummyarg' then return true end return false end ---@param func parser.object ---@param index integer ---@return parser.object? local function getFuncArg(func, index) local args = func.args if not args then return nil end if args[index] then return args[index] end local lastArg = args[#args] if lastArg and lastArg.type == '...' then return lastArg end return nil end ---@param arg parser.object ---@param call parser.object ---@param callNode vm.node ---@param fixIndex integer ---@param myIndex integer local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex) local eventIndex, eventMap if call.args then for i = 1, 2 do local eventArg = call.args[i + fixIndex] if not eventArg then break end eventMap = vm.getLiterals(eventArg) if eventMap then eventIndex = i break end end end for n in callNode:eachObject() do if n.type == 'function' then ---@cast n parser.object local sign = getObjectSign(n) local farg = getFuncArg(n, myIndex) if farg then for fn in vm.compileNode(farg):eachObject() do if isValidCallArgNode(arg, fn) then if fn.type == 'doc.type.function' then ---@cast fn parser.object if sign then local generic = vm.createGeneric(fn, sign) local args = {} for i = fixIndex + 1, myIndex - 1 do args[#args+1] = call.args[i] end fn = generic:resolve(guide.getUri(call), args) end end vm.setNode(arg, fn) end end end end if n.type == 'doc.type.function' then ---@cast n parser.object local myEvent if n.args[eventIndex] then local argNode = vm.compileNode(n.args[eventIndex]) myEvent = argNode:get(1) end if not myEvent or not eventMap or myIndex <= eventIndex or myEvent.type ~= 'doc.type.string' or eventMap[myEvent[1]] then local farg = getFuncArg(n, myIndex) if farg then for fn in vm.compileNode(farg):eachObject() do if isValidCallArgNode(arg, fn) then vm.setNode(arg, fn) end end end end end end end ---@param arg parser.object ---@param call parser.object ---@param index? integer ---@return vm.node? function vm.compileCallArg(arg, call, index) if not index then for i, carg in ipairs(call.args) do if carg == arg then index = i break end end if not index then return nil end end local callNode = vm.compileNode(call.node) compileCallArgNode(arg, call, callNode, 0, index) if call.node.special == 'pcall' or call.node.special == 'xpcall' then local fixIndex = call.node.special == 'pcall' and 1 or 2 if call.args and call.args[1] then callNode = vm.compileNode(call.args[1]) compileCallArgNode(arg, call, callNode, fixIndex, index - fixIndex) end end return vm.getNode(arg) end ---@class parser.object ---@field _iterator? table ---@field _iterArgs? table ---@field _iterVars? table ---@param source parser.object local function compileForVars(source) if source._iterator then return end if not source.exps then return end -- for k, v in pairs(t) do --> for k, v in iterator, status, initValue do --> local k, v = iterator(status, initValue) source._iterator = { type = 'dummyfunc', parent = source, } source._iterArgs = {{},{}} source._iterVars = {} -- iterator selectNode(source._iterator, source.exps, 1) -- status selectNode(source._iterArgs[1], source.exps, 2) -- initValue selectNode(source._iterArgs[2], source.exps, 3) if source.keys then for i, loc in ipairs(source.keys) do local node = getReturn(source._iterator, i, source._iterArgs) node:removeOptional() source._iterVars[loc] = node end end end ---@param source parser.object local function compileLocal(source) vm.setNode(source, source) local hasMarkDoc if source.bindDocs then hasMarkDoc = bindDocs(source) end local hasMarkParam if source.type == 'self' and not hasMarkDoc then hasMarkParam = true if source.parent.type == 'callargs' then -- obj:func(...) if source.parent.parent and source.parent.parent.node and source.parent.parent.node.node then vm.setNode(source, vm.compileNode(source.parent.parent.node.node)) end else -- function obj:func(...) if source.parent.parent and source.parent.parent.parent and source.parent.parent.parent.node then vm.setNode(source, vm.compileNode(source.parent.parent.parent.node)) end end end local hasMarkValue if not hasMarkDoc and source.value then hasMarkValue = true if source.value.type == 'table' then vm.setNode(source, source.value) elseif source.value.type ~= 'nil' then vm.setNode(source, vm.compileNode(source.value)) end end if not hasMarkValue and not hasMarkValue then if source.ref then for _, ref in ipairs(source.ref) do if ref.type == 'setlocal' and ref.value and ref.value.type == 'function' then vm.setNode(source, vm.compileNode(ref.value)) end end end end -- function x.y(self, ...) --> function x:y(...) if source[1] == 'self' and not hasMarkDoc and source.parent.type == 'funcargs' and source.parent[1] == source then local setfield = source.parent.parent.parent if setfield.type == 'setfield' then hasMarkParam = true vm.setNode(source, vm.compileNode(setfield.node)) end end if source.parent.type == 'funcargs' and not hasMarkDoc and not hasMarkParam then local func = source.parent.parent local funcNode = vm.compileNode(func) local hasDocArg for n in funcNode:eachObject() do if n.type == 'doc.type.function' then for index, arg in ipairs(n.args) do if func.args[index] == source then local argNode = vm.compileNode(arg) for an in argNode:eachObject() do if an.type ~= 'doc.generic.name' then vm.setNode(source, an) end end hasDocArg = true end end end end if not hasDocArg then vm.setNode(source, vm.declareGlobal('type', 'any')) end end -- for x in ... do if source.parent.type == 'in' then compileForVars(source.parent) local keyNode = source.parent._iterVars and source.parent._iterVars[source] if keyNode then vm.setNode(source, keyNode) end end -- for x = ... do if source.parent.type == 'loop' then if source.parent.loc == source then vm.setNode(source, vm.declareGlobal('type', 'integer')) end end vm.getNode(source):setData('hasDefined', hasMarkDoc or hasMarkParam or hasMarkValue) end local binarySwich = util.switch() : case 'and' : call(function (source) local node1 = vm.compileNode(source[1]) local node2 = vm.compileNode(source[2]) local r1 = vm.testCondition(source[1]) if r1 == true then vm.setNode(source, node2) elseif r1 == false then vm.setNode(source, node1) else local node = node1:copy():setFalsy():merge(node2) vm.setNode(source, node) end end) : case 'or' : call(function (source) local node1 = vm.compileNode(source[1]) local node2 = vm.compileNode(source[2]) local r1 = vm.testCondition(source[1]) if r1 == true then vm.setNode(source, node1) elseif r1 == false then vm.setNode(source, node2) else local node = node1:copy():setTruthy():merge(node2) vm.setNode(source, node) end end) : case '==' : case '~=' : call(function (source) local result = vm.equal(source[1], source[2]) if result == nil then vm.setNode(source, vm.declareGlobal('type', 'boolean')) else if source.op.type == '~=' then result = not result end vm.setNode(source, { type = 'boolean', start = source.start, finish = source.finish, parent = source, [1] = result, }) end end) : case '<<' : case '>>' : case '&' : case '|' : case '~' : call(function (source) local a = vm.getInteger(source[1]) local b = vm.getInteger(source[2]) if a and b then local op = source.op.type local result = op.type == '<<' and a << b or op.type == '>>' and a >> b or op.type == '&' and a & b or op.type == '|' and a | b or op.type == '~' and a ~ b vm.setNode(source, { type = 'integer', start = source.start, finish = source.finish, parent = source, [1] = result, }) else vm.setNode(source, vm.declareGlobal('type', 'integer')) end end) : case '+' : case '-' : case '*' : case '/' : case '%' : case '//' : case '^' : call(function (source) local a = vm.getNumber(source[1]) local b = vm.getNumber(source[2]) local op = source.op.type local zero = b == 0 and ( op == '%' or op == '/' or op == '//' ) if a and b and not zero then local result = op == '+' and a + b or op == '-' and a - b or op == '*' and a * b or op == '/' and a / b or op == '%' and a % b or op == '//' and a // b or op == '^' and a ^ b vm.setNode(source, { type = math.type(result) == 'integer' and 'integer' or 'number', start = source.start, finish = source.finish, parent = source, [1] = result, }) else if op == '+' or op == '-' or op == '*' or op == '//' or op == '%' then local uri = guide.getUri(source) local infer1 = vm.getInfer(source[1]) local infer2 = vm.getInfer(source[2]) if infer1:hasType(uri, 'integer') or infer2:hasType(uri, 'integer') then if not infer1:hasType(uri, 'number') and not infer2:hasType(uri, 'number') then vm.setNode(source, vm.declareGlobal('type', 'integer')) return end end end vm.setNode(source, vm.declareGlobal('type', 'number')) end end) : case '..' : call(function (source) local a = vm.getString(source[1]) or vm.getNumber(source[1]) local b = vm.getString(source[2]) or vm.getNumber(source[2]) if a and b then if type(a) == 'number' or type(b) == 'number' then local uri = guide.getUri(source) local version = config.get(uri, 'Lua.runtime.version') if math.tointeger(a) and math.type(a) == 'float' then if version == 'Lua 5.3' or version == 'Lua 5.4' then a = ('%.1f'):format(a) else a = ('%.0f'):format(a) end end if math.tointeger(b) and math.type(b) == 'float' then if version == 'Lua 5.3' or version == 'Lua 5.4' then b = ('%.1f'):format(b) else b = ('%.0f'):format(b) end end end vm.setNode(source, { type = 'string', start = source.start, finish = source.finish, parent = source, [1] = a .. b, }) else vm.setNode(source, vm.declareGlobal('type', 'string')) end end) : case '>' : case '<' : case '>=' : case '<=' : call(function (source) local a = vm.getNumber(source[1]) local b = vm.getNumber(source[2]) if a and b then local op = source.op.type local result = op.type == '>' and a > b or op.type == '<' and a < b or op.type == '>=' and a >= b or op.type == '<=' and a <= b vm.setNode(source, { type = 'boolean', start = source.start, finish = source.finish, parent = source, [1] =result, }) else vm.setNode(source, vm.declareGlobal('type', 'boolean')) end end) local compilerSwitch = util.switch() : case 'nil' : case 'boolean' : case 'integer' : case 'number' : case 'string' : case 'doc.type.function' : case 'doc.type.table' : case 'doc.type.array' : call(function (source) vm.setNode(source, source) end) : case 'table' : call(function (source) vm.setNode(source, source) if source.parent.type == 'callargs' then local call = source.parent.parent vm.compileCallArg(source, call) end if source.parent.type == 'setglobal' or source.parent.type == 'local' or source.parent.type == 'setlocal' or source.parent.type == 'tablefield' or source.parent.type == 'tableindex' or source.parent.type == 'tableexp' or source.parent.type == 'setfield' or source.parent.type == 'setindex' then local parentNode = vm.compileNode(source.parent) for _, pn in ipairs(parentNode) do if pn.type == 'global' and pn.cate == 'type' then ---@cast pn vm.global if not guide.isBasicType(pn.name) then vm.setNode(source, pn) end elseif pn.type == 'doc.type.table' or pn.type == 'doc.type.array' then vm.setNode(source, pn) end end end end) : case 'function' : call(function (source) vm.setNode(source, source) if source.bindDocs then for _, doc in ipairs(source.bindDocs) do if doc.type == 'doc.overload' then vm.setNode(source, vm.compileNode(doc)) end end end -- table.sort(string[], function () end) if source.parent.type == 'callargs' then local call = source.parent.parent vm.compileCallArg(source, call) end end) : case 'paren' : call(function (source) if bindAs(source) then return end if source.exp then vm.setNode(source, vm.compileNode(source.exp)) end end) : case 'local' : case 'self' ---@param source parser.object : call(function (source) compileLocal(source) local refs = source.ref if not refs then return end local hasMark = vm.getNode(source):getData 'hasDefined' vm.launchRunner(source, function (src, node) if src.type == 'setlocal' then if src.bindDocs then for _, doc in ipairs(src.bindDocs) do if doc.type == 'doc.type' then vm.setNode(src, vm.compileNode(doc), true) return vm.getNode(src) end end end if src.value then if src.value.type == 'table' then vm.setNode(src, vm.createNode(src.value)) vm.setNode(src, node:copy():asTable()) else vm.setNode(src, vm.compileNode(src.value), true) end else vm.setNode(src, node, true) end return vm.getNode(src) elseif src.type == 'getlocal' then if bindAs(src) then return end vm.setNode(src, node, true) matchCall(src) end end) if not hasMark then local parentFunc = guide.getParentFunction(source) for _, ref in ipairs(source.ref) do if ref.type == 'setlocal' and guide.getParentFunction(ref) == parentFunc then local refNode = vm.getNode(ref) if refNode then vm.setNode(source, refNode) end end end end end) : case 'setlocal' : call(function (source) vm.compileNode(source.node) end) : case 'getlocal' : call(function (source) if bindAs(source) then return end vm.compileNode(source.node) end) : case 'setfield' : case 'setmethod' : case 'setindex' : case 'getfield' : case 'getmethod' : case 'getindex' : call(function (source) if guide.isGet(source) and bindAs(source) then return end ---@type (string|vm.node)? local key = guide.getKeyName(source) if key == nil and source.index then key = vm.compileNode(source.index) end if key == nil then return end if type(key) == 'table' then ---@cast key vm.node local uri = guide.getUri(source) local value = vm.getTableValue(uri, vm.compileNode(source.node), key) if value then vm.setNode(source, value) end for k in key:eachObject() do if k.type == 'global' and k.cate == 'type' then ---@cast k vm.global vm.compileByParentNode(source.node, k, false, function (src) vm.setNode(source, vm.compileNode(src)) if src.value then vm.setNode(source, vm.compileNode(src.value)) end end) end end else ---@cast key string vm.compileByParentNode(source.node, key, false, function (src) if src.value then if bindDocs(src) then vm.setNode(source, vm.compileNode(src)) else vm.setNode(source, vm.compileNode(src.value)) local node = vm.getNode(src) if node then vm.setNode(source, node) end end else vm.setNode(source, vm.compileNode(src)) end end) end end) : case 'setglobal' : call(function (source) if source.node[1] ~= '_ENV' then return end local key = guide.getKeyName(source) vm.compileByParentNode(source.node, key, false, function (src) if src.type == 'doc.type.field' or src.type == 'doc.field' then vm.setNode(source, vm.compileNode(src)) end end) end) : case 'getglobal' : call(function (source) if bindAs(source) then return end if source.node[1] ~= '_ENV' then return end local key = guide.getKeyName(source) vm.compileByParentNode(source.node, key, false, function (src) vm.setNode(source, vm.compileNode(src)) end) end) : case 'tablefield' : case 'tableindex' : call(function (source) local hasMarkDoc if source.bindDocs then hasMarkDoc = bindDocs(source) end if not hasMarkDoc then vm.compileByParentNode(source.node, guide.getKeyName(source), false, function (src) if src.type == 'doc.field' or src.type == 'doc.type.field' then hasMarkDoc = true vm.setNode(source, vm.compileNode(src)) end end) end if not hasMarkDoc and source.value then vm.setNode(source, vm.compileNode(source.value)) end end) : case 'field' : case 'method' : call(function (source) vm.setNode(source, vm.compileNode(source.parent)) end) : case 'tableexp' : call(function (source) if (source.parent.type == 'table') then local node = vm.compileNode(source.parent) for n in node:eachObject() do if n.type == 'doc.type.array' then vm.setNode(source, vm.compileNode(n.node)) end end end vm.setNode(source, vm.compileNode(source.value)) end) : case 'function.return' ---@param source parser.object : call(function (source) local func = source.parent local index = source.returnIndex local hasMarkDoc if func.bindDocs then local sign = getObjectSign(func) local lastReturn for _, doc in ipairs(func.bindDocs) do if doc.type == 'doc.return' then for _, rtn in ipairs(doc.returns) do lastReturn = rtn if rtn.returnIndex == index then hasMarkDoc = true local hasGeneric if sign then guide.eachSourceType(rtn, 'doc.generic.name', function (src) hasGeneric = true end) end if hasGeneric then ---@cast sign -false vm.setNode(source, vm.createGeneric(rtn, sign)) else vm.setNode(source, vm.compileNode(rtn)) end end end end end if lastReturn and not hasMarkDoc then if lastReturn.name and lastReturn.name[1] == '...' then hasMarkDoc = true vm.setNode(source, vm.compileNode(lastReturn)) end end end local hasReturn if func.returns and not hasMarkDoc then for _, rtn in ipairs(func.returns) do if selectNode(source, rtn, index) then hasReturn = true end end if hasReturn then local hasKnownType local hasUnknownType for n in vm.getNode(source):eachObject() do if guide.isLiteral(n) then if n.type ~= 'nil' then hasKnownType = true break end goto CONTINUE end if n.type == 'global' and n.cate == 'type' then if n.name ~= 'nil' then hasKnownType = true break end goto CONTINUE end hasUnknownType = true ::CONTINUE:: end if not hasKnownType and hasUnknownType then vm.setNode(source, vm.declareGlobal('type', 'unknown')) end end end if not hasMarkDoc and not hasReturn then vm.setNode(source, vm.declareGlobal('type', 'nil')) end end) : case 'call.return' ---@param source parser.object : call(function (source) if bindAs(source) then return end local func = source.func local args = source.args local index = source.cindex if func.special == 'setmetatable' then if not args then return end vm.setNode(source, getReturnOfSetMetaTable(args)) return end if func.special == 'pcall' and index > 1 then if not args then return end local newArgs = {} for i = 2, #args do newArgs[#newArgs+1] = args[i] end local node = getReturn(args[1], index - 1, newArgs) if node then vm.setNode(source, node) end return end if func.special == 'xpcall' and index > 1 then if not args then return end local newArgs = {} for i = 3, #args do newArgs[#newArgs+1] = args[i] end local node = getReturn(args[1], index - 1, newArgs) if node then vm.setNode(source, node) end return end if func.special == 'require' then if not args then return end local nameArg = args[1] if not nameArg or nameArg.type ~= 'string' then return end local name = nameArg[1] if not name or type(name) ~= 'string' then return end local uri = rpath.findUrisByRequirePath(guide.getUri(func), name)[1] if not uri then return end local state = files.getState(uri) local ast = state and state.ast if not ast then return end vm.setNode(source, vm.compileNode(ast)) return end local funcNode = vm.compileNode(func) ---@type vm.node? for mfunc in funcNode:eachObject() do if mfunc.type == 'function' or mfunc.type == 'doc.type.function' then ---@cast mfunc parser.object local returnObject = vm.getReturnOfFunction(mfunc, index) if returnObject then local returnNode = vm.compileNode(returnObject) for rnode in returnNode:eachObject() do if rnode.type == 'generic' then returnNode = rnode:resolve(guide.getUri(func), args) break end end if returnNode then for rnode in returnNode:eachObject() do -- TODO: narrow type if rnode.type ~= 'doc.generic.name' then vm.setNode(source, rnode) end end if returnNode:isOptional() then vm.getNode(source):addOptional() end end end end end end) : case 'main' : call(function (source) if source.returns then for _, rtn in ipairs(source.returns) do if rtn[1] then vm.setNode(source, vm.compileNode(rtn[1])) end end end end) : case 'select' : call(function (source) local vararg = source.vararg if vararg.type == 'call' then local node = getReturn(vararg.node, source.sindex, vararg.args) if not node then return end vm.setNode(source, node) end if vararg.type == 'varargs' then vm.setNode(source, vm.compileNode(vararg)) end end) : case 'varargs' : call(function (source) if source.node then vm.setNode(source, vm.compileNode(source.node)) end end) : case 'call' : call(function (source) local node = getReturn(source.node, 1, source.args) if not node then return end vm.setNode(source, node) end) : case 'doc.type' : call(function (source) for _, typeUnit in ipairs(source.types) do vm.setNode(source, vm.compileNode(typeUnit)) end if source.optional then vm.getNode(source):addOptional() end end) : case 'doc.type.integer' : case 'doc.type.string' : case 'doc.type.boolean' : case 'doc.type.code' : call(function (source) vm.setNode(source, source) end) : case 'doc.generic.name' : call(function (source) vm.setNode(source, source) end) : case 'doc.type.sign' : call(function (source) local uri = guide.getUri(source) vm.setNode(source, source) if not source.node[1] then return end local global = vm.getGlobal('type', source.node[1]) if not global then return end for _, set in ipairs(global:getSets(uri)) do if set.type == 'doc.class' then if set.extends then for _, ext in ipairs(set.extends) do if ext.type == 'doc.type.table' then if ext._generic then local resolved = ext._generic:resolve(uri, source.signs) vm.setNode(source, resolved) end end end end end if set.type == 'doc.alias' then if set.extends._generic then local resolved = set.extends._generic:resolve(uri, source.signs) vm.setNode(source, resolved) end end end end) : case 'doc.class.name' : call(function (source) vm.setNode(source, vm.compileNode(source.parent)) end) : case 'doc.field' : call(function (source) if not source.extends then return end local fieldNode = vm.compileNode(source.extends) if source.optional then fieldNode:addOptional() end vm.setNode(source, fieldNode) end) : case 'doc.type.field' : call(function (source) if not source.extends then return end local fieldNode = vm.compileNode(source.extends) if source.optional then fieldNode:addOptional() end vm.setNode(source, fieldNode) end) : case 'doc.param' : call(function (source) if not source.extends then return end vm.setNode(source, vm.compileNode(source.extends)) end) : case 'doc.vararg' : call(function (source) if not source.vararg then return end vm.setNode(source, vm.compileNode(source.vararg)) end) : case '...' : call(function (source) local func = source.parent.parent if func.type ~= 'function' then return end if not func.bindDocs then return end for _, doc in ipairs(func.bindDocs) do if doc.type == 'doc.vararg' then vm.setNode(source, vm.compileNode(doc)) end if doc.type == 'doc.param' and doc.param[1] == '...' then vm.setNode(source, vm.compileNode(doc)) end end end) : case 'doc.overload' : call(function (source) vm.setNode(source, vm.compileNode(source.overload)) end) : case 'doc.see.name' : call(function (source) local type = vm.getGlobal('type', source[1]) if type then vm.setNode(source, type) end end) : case 'doc.type.arg' : call(function (source) if source.extends then vm.setNode(source, vm.compileNode(source.extends)) else vm.setNode(source, vm.declareGlobal('type', 'any')) end if source.optional then vm.getNode(source):addOptional() end end) : case 'unary' : call(function (source) if bindAs(source) then return end if not source[1] then return end if source.op.type == 'not' then local result = vm.testCondition(source[1]) if result == nil then vm.setNode(source, vm.declareGlobal('type', 'boolean')) return else vm.setNode(source, { type = 'boolean', start = source.start, finish = source.finish, parent = source, [1] = not result, }) return end end if source.op.type == '#' then vm.setNode(source, vm.declareGlobal('type', 'integer')) return end if source.op.type == '-' then local v = vm.getNumber(source[1]) if v == nil then local infer = vm.getInfer(source[1]) if infer:hasType(guide.getUri(source), 'integer') then vm.setNode(source, vm.declareGlobal('type', 'integer')) else vm.setNode(source, vm.declareGlobal('type', 'number')) end return else vm.setNode(source, { type = 'number', start = source.start, finish = source.finish, parent = source, [1] = -v, }) return end end if source.op.type == '~' then local v = vm.getInteger(source[1]) if v == nil then vm.setNode(source, vm.declareGlobal('type', 'integer')) return else vm.setNode(source, { type = 'integer', start = source.start, finish = source.finish, parent = source, [1] = ~v, }) return end end end) : case 'binary' : call(function (source) if bindAs(source) then return end if not source[1] or not source[2] then return end binarySwich(source.op.type, source) end) ---@param source parser.object local function compileByNode(source) compilerSwitch(source.type, source) end ---@param source parser.object local function compileByGlobal(source) local global = source._globalNode if not global then return end ---@cast source parser.object local root = guide.getRoot(source) local uri = guide.getUri(source) if not root._globalBase then root._globalBase = {} end local name = global:asKeyName() if not root._globalBase[name] then root._globalBase[name] = { type = 'globalbase', parent = root, } end local globalNode = vm.getNode(root._globalBase[name]) if globalNode then vm.setNode(source, globalNode, true) return end ---@type vm.node globalNode = vm.createNode(global) vm.setNode(root._globalBase[name], globalNode, true) vm.setNode(source, globalNode, true) -- TODO:don't mix --local sets = global.links[uri].sets or {} --local gets = global.links[uri].gets or {} --for _, set in ipairs(sets) do -- vm.setNode(set, globalNode, true) --end --for _, get in ipairs(gets) do -- vm.setNode(get, globalNode, true) --end if global.cate == 'variable' then local hasMarkDoc for _, set in ipairs(global:getSets(uri)) do if set.bindDocs then if bindDocs(set) then globalNode:merge(vm.compileNode(set)) hasMarkDoc = true end if vm.getNode(set) then globalNode:merge(vm.compileNode(set)) end end end -- Set all globals node first to avoid recursive for _, set in ipairs(global:getSets(uri)) do vm.setNode(set, globalNode, true) end for _, set in ipairs(global:getSets(uri)) do if set.value then if not hasMarkDoc or guide.isLiteral(set.value) then globalNode:merge(vm.compileNode(set.value)) end end end for _, set in ipairs(global:getSets(uri)) do vm.setNode(set, globalNode, true) end end if global.cate == 'type' then for _, set in ipairs(global:getSets(uri)) do if set.type == 'doc.class' then if set.extends then for _, ext in ipairs(set.extends) do if ext.type == 'doc.type.table' then if not ext._generic then globalNode:merge(vm.compileNode(ext)) end end end end end if set.type == 'doc.alias' then if not set.extends._generic then globalNode:merge(vm.compileNode(set.extends)) end end end end end ---@param source vm.object ---@return vm.node function vm.compileNode(source) if not source then if TEST then error('Can not compile nil source') else log.error('Can not compile nil source') end end local cache = vm.getNode(source) if cache ~= nil then return cache end if source.type == 'generic' then vm.setNode(source, source) local node = vm.getNode(source) ---@cast node -? return node end ---@cast source parser.object vm.setNode(source, vm.createNode(), true) compileByGlobal(source) compileByNode(source) matchCall(source) local node = vm.getNode(source) ---@cast node -? return node end