.dotfiles/nvim/mason/packages/lua-language-server/libexec/script/vm/compiler.lua

1964 lines
62 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 cindex integer
---@field func parser.object
-- 该函数有副作用会给source绑定node
---@param source parser.object
---@return boolean
function vm.bindDocs(source)
local docs = source.bindDocs
if not docs then
return false
end
for i = #docs, 1, -1 do
local doc = docs[i]
if doc.type == 'doc.type' then
vm.setNode(source, vm.compileNode(doc))
return true
end
if doc.type == 'doc.class' then
vm.setNode(source, vm.compileNode(doc))
return true
end
if doc.type == 'doc.param' then
local node = vm.compileNode(doc)
if doc.optional then
node:addOptional()
end
vm.setNode(source, node)
return true
end
if doc.type == 'doc.module' then
local name = doc.module
if not name then
return true
end
local uri = rpath.findUrisByRequireName(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
vm.setNode(source, vm.compileNode(doc))
end
end
return false
end
---@param source parser.object | vm.variable
---@param key string|vm.global|vm.ANY
---@param pushResult fun(res: parser.object, markDoc?: boolean)
local function searchFieldByLocalID(source, key, pushResult)
local fields
if key ~= vm.ANY then
if type(key) ~= 'string' then
return
end
if source.type == 'variable' then
---@cast source vm.variable
fields = source:getSets(key)
else
---@cast source parser.object
fields = vm.getVariableSets(source, key)
end
else
if source.type == 'variable' then
---@cast source vm.variable
fields = source:getFields(false)
else
---@cast source parser.object
fields = vm.getVariableFields(source, false)
end
end
if not fields then
return
end
local hasMarkDoc = {}
for _, src in ipairs(fields) do
if src.bindDocs then
if vm.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
---@param suri uri
---@param source parser.object
---@param key string|vm.global|vm.ANY
---@param pushResult fun(res: parser.object, markDoc?: boolean)
local function searchFieldByGlobalID(suri, source, key, pushResult)
local node = vm.getGlobalNode(source)
if not node then
return
end
if node.cate == 'variable' then
if key ~= vm.ANY 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
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
end
end
end
if node.cate == 'type' then
vm.getClassFields(suri, node, key, pushResult)
end
end
local searchFieldSwitch = util.switch()
: case 'table'
: call(function (suri, source, key, 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 == vm.ANY
or key == fieldKey then
hasFiled = true
pushResult(field)
end
end
if field.type == 'tableexp' then
if key == vm.ANY
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 == vm.ANY then
pushResult(field)
end
end
end
end)
: case 'string'
: case 'doc.type.string'
: call(function (suri, source, key, pushResult)
-- change to `string: stringlib` ?
local stringlib = vm.getGlobal('type', 'stringlib')
if stringlib then
vm.getClassFields(suri, stringlib, key, pushResult)
end
end)
: case 'doc.type.array'
: call(function (suri, source, key, pushResult)
if type(key) == 'number' then
if key < 1
or not math.tointeger(key) then
return
end
pushResult(source.node, true)
end
if type(key) == 'table' then
if vm.isSubType(suri, key, 'integer') then
pushResult(source.node, true)
end
end
end)
: case 'doc.type.table'
: call(function (suri, source, key, pushResult)
if type(key) == 'string' and key:find(vm.ID_SPLITE) then
return
end
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 == vm.ANY
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, true)
end
elseif fn.type == 'doc.type.string'
or fn.type == 'doc.type.integer'
or fn.type == 'doc.type.boolean' then
if key == vm.ANY
or fn[1] == key then
pushResult(field, true)
end
end
end
end
if fieldKey.type == 'doc.field.name' then
if key == vm.ANY or fieldKey[1] == key then
pushResult(field, true)
end
end
end
end)
: case 'global'
: call(function (suri, node, key, pushResult)
if node.cate == 'variable' then
if key ~= vm.ANY 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
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
end
end
end
if node.cate == 'type' then
vm.getClassFields(suri, node, key, pushResult)
end
end)
: default(function (suri, source, key, pushResult)
searchFieldByLocalID(source, key, pushResult)
searchFieldByGlobalID(suri, source, key, pushResult)
end)
---@param suri uri
---@param object vm.global
---@param key string|number|integer|boolean|vm.global|vm.ANY
---@param pushResult fun(field: vm.object, isMark?: boolean)
function vm.getClassFields(suri, object, key, pushResult)
local mark = {}
local function searchClass(class, searchedFields)
local name = class.name
if mark[name] then
return
end
mark[name] = true
searchedFields = searchedFields or {}
local hasFounded = {}
local function copyToSearched()
for fieldKey in pairs(hasFounded) do
searchedFields[fieldKey] = true
hasFounded[fieldKey] = nil
end
end
local sets = class:getSets(suri)
for _, set in ipairs(sets) do
if set.type == 'doc.class' then
-- check ---@field
for _, field in ipairs(set.fields) do
local fieldKey = guide.getKeyName(field)
if fieldKey then
-- ---@field x boolean -> class.x
if key == vm.ANY
or fieldKey == key then
if not searchedFields[fieldKey] then
pushResult(field, true)
hasFounded[fieldKey] = true
end
end
goto CONTINUE
end
if key == vm.ANY then
pushResult(field, true)
goto CONTINUE
end
if hasFounded[key] then
goto CONTINUE
end
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 keyObject
if keyType == 'number' then
if math.tointeger(key) then
keyObject = { type = 'integer', [1] = key }
else
keyObject = { type = 'number', [1] = key }
end
elseif keyType == 'boolean'
or keyType == 'string' then
keyObject = { type = keyType, [1] = key }
end
if keyObject and field.field.type ~= 'doc.field.name' then
-- ---@field [integer] boolean -> class[1]
local fieldNode = vm.compileNode(field.field)
if vm.isSubType(suri, keyObject, fieldNode) then
local nkey = '|' .. keyType
if not searchedFields[nkey] then
pushResult(field, true)
hasFounded[nkey] = true
end
end
end
end
::CONTINUE::
end
end
end
copyToSearched()
for _, set in ipairs(sets) do
if set.type == 'doc.class' then
-- check local field and global field
if not searchedFields[key] and set.bindSource then
local src = set.bindSource
if src.value and src.value.type == 'table' then
searchFieldSwitch('table', suri, src.value, key, function (field)
local fieldKey = guide.getKeyName(field)
if fieldKey then
if not searchedFields[fieldKey]
and guide.isAssign(field) then
hasFounded[fieldKey] = true
pushResult(field, true)
end
end
end)
end
if src.value
and src.value.type == 'select'
and src.value.vararg.type == 'call' then
local func = src.value.vararg.node
local args = src.value.vararg.args
if func.special == 'setmetatable'
and args
and args[1]
and args[1].type == 'table' then
searchFieldSwitch('table', suri, args[1], key, function (field)
local fieldKey = guide.getKeyName(field)
if fieldKey then
if not searchedFields[fieldKey]
and guide.isAssign(field) then
hasFounded[fieldKey] = true
pushResult(field, true)
end
end
end)
end
end
end
end
end
copyToSearched()
for _, set in ipairs(sets) do
if set.type == 'doc.class' then
if not searchedFields[key] and set.bindSource then
local src = set.bindSource
searchFieldSwitch(src.type, suri, src, key, function (field)
local fieldKey = guide.getKeyName(field)
if fieldKey and not searchedFields[fieldKey] then
if not searchedFields[fieldKey]
and guide.isAssign(field)
and field.value then
if vm.getVariableID(field)
and vm.getVariableID(field) == vm.getVariableID(field.value) then
elseif vm.getGlobalNode(src)
and vm.getGlobalNode(src) == vm.getGlobalNode(field.value) then
else
hasFounded[fieldKey] = true
end
pushResult(field, true)
end
end
end)
end
end
end
copyToSearched()
for _, set in ipairs(sets) do
if set.type == 'doc.class' then
-- look into extends(if field not found)
if not searchedFields[key] and set.extends then
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
copyToSearched()
end
local function searchGlobal(class)
if class.cate == 'type' and class.name == '_G' then
if key == vm.ANY 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
---@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,
}
vm.compileNode(func._returns[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 = vm.getSign(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.compileByParentNodeAll(mt, '__index', 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
--过滤nil
node:remove 'nil'
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)
newNode.originNode = myNode
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
function vm.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
local asNode = vm.compileNode(doc.as)
vm.setNode(source, asNode, true)
return true
end
return false
end
---@param source parser.object | vm.variable
---@param key string|vm.global|vm.ANY
---@return parser.object[] docedResults
---@return parser.object[] commonResults
function vm.getNodesOfParentNode(source, key)
local parentNode = vm.compileNode(source)
local docedResults = {}
local commonResults = {}
local mark = {}
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, function (res, markDoc)
if mark[res] then
return
end
mark[res] = true
if markDoc then
docedResults[#docedResults+1] = res
else
commonResults[#commonResults+1] = res
end
end)
end
end
if not next(mark) then
searchFieldByLocalID(source, key, function (res, markDoc)
if mark[res] then
return
end
mark[res] = true
if markDoc then
docedResults[#docedResults+1] = res
else
commonResults[#commonResults+1] = res
end
end)
end
return docedResults, commonResults
end
-- 遍历所有字段(按照优先级)
---@param source parser.object | vm.variable
---@param key string|vm.global|vm.ANY
---@param pushResult fun(source: parser.object)
function vm.compileByParentNode(source, key, pushResult)
local docedResults, commonResults = vm.getNodesOfParentNode(source, key)
if #docedResults > 0 then
for _, res in ipairs(docedResults) do
pushResult(res)
end
end
if #docedResults == 0 or key == vm.ANY then
for _, res in ipairs(commonResults) do
pushResult(res)
end
end
end
-- 遍历所有字段(无视优先级)
---@param source parser.object | vm.variable
---@param key string|vm.global|vm.ANY
---@param pushResult fun(source: parser.object)
function vm.compileByParentNodeAll(source, key, pushResult)
local docedResults, commonResults = vm.getNodesOfParentNode(source, key)
for _, res in ipairs(docedResults) do
pushResult(res)
end
for _, res in ipairs(commonResults) do
pushResult(res)
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 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)
---@type integer?, table<any, boolean>?
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 = vm.getSign(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
local resolvedNode = generic:resolve(guide.getUri(call), args)
vm.setNode(arg, resolvedNode)
goto CONTINUE
end
end
vm.setNode(arg, fn)
::CONTINUE::
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 package _iterator? table
---@field package _iterArgs? table
---@field package _iterVars? table<parser.object, vm.node>
---@param source parser.object
---@param target parser.object
---@return boolean
local function compileForVars(source, target)
if not source.exps then
return false
end
-- for k, v in pairs(t) do
--> for k, v in iterator, status, initValue do
--> local k, v = iterator(status, initValue)
if not source._iterator then
source._iterator = {
type = 'dummyfunc',
parent = source,
}
source._iterArgs = {{},{}}
source._iterVars = {}
end
-- iterator
if not vm.getNode(source._iterator) then
selectNode(source._iterator, source.exps, 1)
end
-- status
if not vm.getNode(source._iterArgs[1]) then
selectNode(source._iterArgs[1], source.exps, 2)
end
-- initValue
if not vm.getNode(source._iterArgs[2]) then
selectNode(source._iterArgs[2], source.exps, 3)
end
if source.keys then
for i, loc in ipairs(source.keys) do
if loc == target then
local node = getReturn(source._iterator, i, source._iterArgs)
node:removeOptional()
vm.setNode(loc, node)
return true
end
end
end
return false
end
---@param source parser.object
local function compileLocal(source)
local myNode = vm.setNode(source, source)
local hasMarkDoc
if source.bindDocs then
hasMarkDoc = vm.bindDocs(source)
end
local hasMarkParam
if not hasMarkDoc then
local selfNode = guide.getSelfNode(source)
if selfNode then
hasMarkParam = true
vm.setNode(source, vm.compileNode(selfNode))
myNode:remove 'function'
end
end
local hasMarkValue
if (not hasMarkDoc and source.value)
or (source.value and source.value.type == 'table') 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
-- 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 call ---@type fun(f: fun(x: number));call(function (x) end) --> x -> number
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, source)
hasMarkDoc = true
end
-- for x = ... do
if source.parent.type == 'loop' then
if source.parent.loc == source then
if vm.bindDocs(source) then
return
end
vm.setNode(source, vm.declareGlobal('type', 'integer'))
hasMarkDoc = true
end
end
if not hasMarkDoc
and not hasMarkValue
and source.ref then
local firstSet
local myFunction = guide.getParentFunction(source)
for _, ref in ipairs(source.ref) do
if ref.type == 'setlocal' then
firstSet = ref
break
end
if ref.type == 'getlocal' then
if guide.getParentFunction(ref) == myFunction then
break
end
end
end
if firstSet
and guide.getBlock(firstSet) == guide.getBlock(source) then
vm.setNode(source, vm.compileNode(firstSet))
end
end
myNode.hasDefined = hasMarkDoc or hasMarkParam or hasMarkValue
end
---@param source parser.object
---@param mfunc parser.object
---@param index integer
---@param args parser.object[]
local function bindReturnOfFunction(source, mfunc, index, args)
local returnObject = vm.getReturnOfFunction(mfunc, index)
if not returnObject then
return
end
local returnNode = vm.compileNode(returnObject)
for rnode in returnNode:eachObject() do
if rnode.type == 'generic' then
returnNode = rnode:resolve(guide.getUri(source), 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
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)
local parent = source.parent
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 (<?x?>) end)
if parent.type == 'callargs' then
local call = parent.parent
vm.compileCallArg(source, call)
end
-- function f() return function (<?x?>) end end
if parent.type == 'return' then
for i, ret in ipairs(parent) do
if ret == source then
local func = guide.getParentFunction(parent)
if func then
local returnObj = vm.getReturnOfFunction(func, i)
if returnObj then
vm.setNode(source, vm.compileNode(returnObj))
end
end
break
end
end
end
-- { f = function (<?x?>) end }
if guide.isAssign(parent)
and parent.value == source then
vm.setNode(source, vm.compileNode(parent))
end
end)
: case 'paren'
: call(function (source)
if vm.bindAs(source) then
return
end
if source.exp then
vm.setNode(source, vm.compileNode(source.exp))
end
end)
: case 'local'
: case 'self'
---@async
---@param source parser.object
: call(function (source)
compileLocal(source)
end)
: case 'setlocal'
: call(function (source)
if vm.bindDocs(source) then
return
end
local locNode = vm.compileNode(source.node)
if not source.value then
vm.setNode(source, locNode)
return
end
local valueNode = vm.compileNode(source.value)
vm.setNode(source, valueNode)
if locNode.hasDefined
and guide.isLiteral(source.value) then
vm.setNode(source, locNode)
vm.getNode(source):narrow(guide.getUri(source), source.value.type)
else
vm.setNode(source, valueNode)
end
end)
: case 'getlocal'
---@async
: call(function (source)
if vm.bindAs(source) then
return
end
local node = vm.traceNode(source)
if not node then
return
end
vm.setNode(source, node, true)
end)
: case 'setfield'
: case 'setmethod'
: case 'setindex'
: case 'getfield'
: case 'getmethod'
: case 'getindex'
: call(function (source)
if guide.isGet(source) and vm.bindAs(source) then
return
end
if vm.bindDocs(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, function (src)
vm.setNode(source, vm.compileNode(src))
end)
end
end
else
---@cast key string
vm.compileByParentNode(source.node, key, function (src)
vm.setNode(source, vm.compileNode(src))
if src == source and source.value and source.value.type ~= 'nil' then
vm.setNode(source, vm.compileNode(source.value))
end
end)
end
end)
: case 'setglobal'
: call(function (source)
if vm.bindDocs(source) then
return
end
if source.node[1] ~= '_ENV' then
return
end
if not source.value then
return
end
vm.setNode(source, vm.compileNode(source.value))
end)
: case 'getglobal'
: call(function (source)
if vm.bindAs(source) then
return
end
if source.node[1] ~= '_ENV' then
return
end
local key = guide.getKeyName(source)
if not key then
return
end
vm.compileByParentNode(source.node, key, function (src)
vm.setNode(source, vm.compileNode(src))
end)
end)
: case 'tablefield'
: case 'tableindex'
: call(function (source)
local hasMarkDoc
if source.bindDocs then
hasMarkDoc = vm.bindDocs(source)
end
local key = guide.getKeyName(source)
if not hasMarkDoc then
if key then
vm.compileByParentNode(source.node, key, function (src)
if src.type == 'doc.field'
or src.type == 'doc.type.field'
or src.type == 'doc.type.name' then
hasMarkDoc = true
vm.setNode(source, vm.compileNode(src))
end
end)
end
end
if not hasMarkDoc and source.type == 'tableindex' then
vm.compileByParentNode(source.node, vm.ANY, function (src)
if src.type == 'doc.field'
or src.type == 'doc.type.field' then
if vm.isSubType(guide.getUri(source), vm.compileNode(source.index), vm.compileNode(src.field or src.name)) then
hasMarkDoc = true
vm.setNode(source, vm.compileNode(src))
end
end
end)
end
if source.value then
if not hasMarkDoc
or (type(key) == 'string' and util.stringStartWith(key, '__')) then
vm.setNode(source, vm.compileNode(source.value))
end
end
end)
: case 'field'
: case 'method'
: call(function (source)
vm.setNode(source, vm.compileNode(source.parent))
end)
: case 'tableexp'
: call(function (source)
local hasMarkDoc
vm.compileByParentNode(source.parent, source.tindex, function (src)
if src.type == 'doc.field'
or src.type == 'doc.type.field'
or src.type == 'doc.type.name'
or src.type == 'doc.type'
or guide.isLiteral(src) then
hasMarkDoc = true
vm.setNode(source, vm.compileNode(src))
end
end)
if not hasMarkDoc then
vm.setNode(source, vm.compileNode(source.value))
end
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 = vm.getSign(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
source.comment = doc.comment
if rtn.name then
source.name = rtn.name[1]
end
local hasGeneric
if sign then
guide.eachSourceType(rtn, 'doc.generic.name', function (src)
hasGeneric = true
end)
end
if hasGeneric then
---@cast sign -?
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 vm.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 index == 2 then
local uri = guide.getUri(source)
local version = config.get(uri, 'Lua.runtime.version')
if version == 'Lua 5.3'
or version == 'Lua 5.4' then
vm.setNode(source, vm.declareGlobal('type', 'unknown'))
else
vm.setNode(source, vm.declareGlobal('type', 'nil'))
end
return
end
if index >= 3 then
vm.setNode(source, vm.declareGlobal('type', 'nil'))
return
end
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.findUrisByRequireName(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 nd in funcNode:eachObject() do
if nd.type == 'function'
or nd.type == 'doc.type.function' then
---@cast nd parser.object
bindReturnOfFunction(source, nd, index, args)
elseif nd.type == 'global' and nd.cate == 'type' then
---@cast nd vm.global
for _, set in ipairs(nd:getSets(guide.getUri(source))) do
if set.type == 'doc.class' then
for _, overload in ipairs(set.calls) do
bindReturnOfFunction(source, overload.overload, index, args)
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
if not node:isTyped() then
node = vm.runOperator('call', vararg.node) or node
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
if not node:isTyped() then
node = vm.runOperator('call', source.node) or node
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.type.name'
: call(function (source)
if source[1] == 'self' then
local state = guide.getDocState(source)
if state.type == 'doc.return'
or state.type == 'doc.param' then
local func = state.bindSource
if func.type == 'function' then
local node = guide.getFunctionSelfNode(func)
if node then
vm.setNode(source, vm.compileNode(node))
return
end
end
elseif state.type == 'doc.field'
or state.type == 'doc.overload' then
local class = state.class
if class then
vm.setNode(source, vm.compileNode(class))
return
end
end
end
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 vm.getGeneric(ext) then
local resolved = vm.getGeneric(ext):resolve(uri, source.signs)
vm.setNode(source, resolved)
end
end
end
end
end
if set.type == 'doc.alias' then
if vm.getGeneric(set.extends) then
local resolved = vm.getGeneric(set.extends):resolve(uri, source.signs)
vm.setNode(source, resolved)
end
end
end
end)
: case 'doc.class.name'
: case 'doc.alias.name'
: call(function (source)
vm.setNode(source, vm.compileNode(source.parent))
end)
: case 'doc.enum.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.field.name'
: call(function (source)
vm.setNode(source, source)
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)
if not source.bindDocs then
return
end
for _, doc in ipairs(source.bindDocs) do
if doc.type == 'doc.vararg' then
vm.setNode(source, vm.compileNode(doc))
end
if doc.type == 'doc.param' 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.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 vm.bindAs(source) then
return
end
if not source[1] then
return
end
vm.unarySwich(source.op.type, source)
end)
: case 'binary'
: call(function (source)
if vm.bindAs(source) then
return
end
if not source[1] or not source[2] then
return
end
vm.binarySwitch(source.op.type, source)
end)
: case 'globalbase'
: call(function (source)
---@type vm.global
local global = source.global
local uri = guide.getUri(source)
vm.setNode(source, global)
if global.cate == 'variable' then
for luri, link in pairs(global.links) do
local firstSet = link.sets[1]
if firstSet then
local setNode = vm.compileNode(firstSet)
vm.setNode(source, setNode)
if vm.isMetaFile(luri) then
for i = 2, #link.sets do
setNode = vm.compileNode(link.sets[i])
vm.setNode(source, setNode)
end
end
end
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 vm.getGeneric(ext) then
vm.setNode(source, vm.compileNode(ext))
end
end
end
end
end
if set.type == 'doc.alias' then
if not vm.getGeneric(set.extends) then
vm.setNode(source, vm.compileNode(set.extends))
end
end
end
end
end)
: case 'variable'
---@param variable vm.variable
: call(function (variable)
if variable == vm.getVariable(variable.base) then
vm.setNode(variable, vm.compileNode(variable.base))
return
end
end)
: case 'global'
: case 'generic'
: call(function (source)
vm.setNode(source, source)
end)
---@param source parser.object
local function compileByNode(source)
compilerSwitch(source.type, source)
end
local nodeSwitch;nodeSwitch = util.switch()
: case 'field'
: case 'method'
: call(function (source, lastKey, pushResult)
return nodeSwitch(source.parent.type, source.parent, lastKey, pushResult)
end)
: case 'getfield'
: case 'setfield'
: case 'getmethod'
: case 'setmethod'
: case 'getindex'
: case 'setindex'
: call(function (source, lastKey, pushResult)
local parentNode = vm.compileNode(source.node)
local uri = guide.getUri(source)
local key = guide.getKeyName(source)
if type(key) ~= 'string' then
return
end
if lastKey then
key = key .. vm.ID_SPLITE .. lastKey
end
for pn in parentNode:eachObject() do
searchFieldSwitch(pn.type, uri, pn, key, pushResult)
end
return key, source.node
end)
: case 'tableindex'
: case 'tablefield'
: call(function (source, lastKey, pushResult)
if lastKey then
return
end
local key = guide.getKeyName(source)
if type(key) ~= 'string' then
return
end
local uri = guide.getUri(source)
local parentNode = vm.compileNode(source.node)
for pn in parentNode:eachObject() do
searchFieldSwitch(pn.type, uri, pn, key, pushResult)
end
end)
function vm.compileByNodeChain(source, pushResult)
local lastKey
local src = source
while true do
local key, node = nodeSwitch(src.type, src, lastKey, pushResult)
if not key then
break
end
src = node
lastKey = key
end
end
---@param source vm.object
local function compileByParentNode(source)
if vm.getNode(source):isTyped() then
return
end
vm.compileByNodeChain(source, function (result)
vm.setNode(source, vm.compileNode(result))
end)
end
---@param source vm.node.object | vm.variable
---@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
---@cast source parser.object
vm.setNode(source, vm.createNode(), true)
vm.compileByGlobal(source)
vm.compileByVariable(source)
compileByNode(source)
compileByParentNode(source)
matchCall(source)
local node = vm.getNode(source)
---@cast node -?
return node
end