From 2b6151a88e888decec42fe14b767364210b983ad Mon Sep 17 00:00:00 2001 From: Jakit Liang Date: Thu, 22 May 2025 01:57:43 +0800 Subject: [PATCH 1/2] Add support for `getmetatable` --- changelog.md | 1 + script/parser/compile.lua | 1 + script/vm/compiler.lua | 101 +++++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index d929b7919..3196ce567 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ * `FIX` cannot debug in Linux due to lua-debug expecting host process to have lua54 symbols available * `FIX` support hex color codes with `#` in `textDocument/documentColor` +* `NEW` type reference from `getmetatable` is supported now ## 3.14.0 `2025-4-7` diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 41b0da17e..76f006a89 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -108,6 +108,7 @@ local Specials = { ['rawset'] = true, ['rawget'] = true, ['setmetatable'] = true, + ['getmetatable'] = true, ['require'] = true, ['dofile'] = true, ['loadfile'] = true, diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 986e85e2d..07762b4c4 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -632,9 +632,21 @@ local function getReturnOfSetMetaTable(args) local mt = args[2] local node = vm.createNode() if tbl then - node:merge(vm.compileNode(tbl)) + local tblNode = vm.compileNode(tbl) + node:merge(tblNode) + + -- 存储元表信息,将mt节点作为源节点的元表 + if mt then + local mtNode = vm.compileNode(mt) + -- 为节点添加metatable属性 + for n in tblNode:eachObject() do + n.metatable = mtNode + end + end end + if mt then + -- 合并__index属性到返回节点 vm.compileByParentNodeAll(mt, '__index', function (src) for n in vm.compileNode(src):eachObject() do if n.type == 'global' @@ -647,7 +659,85 @@ local function getReturnOfSetMetaTable(args) end) end --过滤nil - node:remove 'nil' + node:remove 'nil' + return node +end + +---@param args parser.object[] +---@return vm.node +local function getReturnOfGetMetaTable(args) + local obj = args[1] + local node = vm.createNode() + if not obj then + return node + end + + local objNode = vm.compileNode(obj) + local foundMetatable = false + + -- 尝试遍历对象的所有可能类型 + for n in objNode:eachObject() do + -- 检查是否有metatable属性 + if n.metatable then + -- 先检查元表中是否有__metatable字段 + local hasMetaField = false + for mt in n.metatable:eachObject() do + if mt.type == 'table' then + -- 尝试从元表中查找__metatable字段 + vm.compileByParentNodeAll(mt, '__metatable', function (src) + hasMetaField = true + -- 使用__metatable字段的值作为返回值 + for metaObj in vm.compileNode(src):eachObject() do + node:merge(metaObj) + end + end) + end + end + + -- 如果没有__metatable字段,则返回元表本身 + if not hasMetaField then + node:merge(n.metatable) + end + + foundMetatable = true + end + + -- 检查是否有setmetatable调用 + if not foundMetatable and n.value and n.value.type == 'call' and n.value.node and n.value.node.special == 'setmetatable' and n.value.args and n.value.args[2] then + local mtArg = n.value.args[2] + local mtNode = vm.compileNode(mtArg) + + -- 检查元表参数中是否有__metatable字段 + local hasMetaField = false + -- 尝试从元表参数中查找__metatable字段 + vm.compileByParentNodeAll(mtArg, '__metatable', function (src) + hasMetaField = true + -- 使用__metatable字段的值作为返回值 + for metaObj in vm.compileNode(src):eachObject() do + node:merge(metaObj) + end + end) + + -- 如果没有__metatable字段,则返回元表本身 + if not hasMetaField then + node:merge(mtNode) + end + + foundMetatable = true + end + end + + -- 如果没有找到任何元表信息,创建一个空表类型 + if not foundMetatable then + local tableObj = { + type = 'table', + start = obj.start or 0, + finish = obj.finish or 0, + } + node:merge(tableObj) + end + + node:remove 'nil' return node end @@ -1865,6 +1955,13 @@ local compilerSwitch = util.switch() vm.setNode(source, getReturnOfSetMetaTable(args)) return end + if func.special == 'getmetatable' then + if not args then + return + end + vm.setNode(source, getReturnOfGetMetaTable(args)) + return + end if func.special == 'pcall' and index > 1 then if not args then return From 72741f6faf7ea7395a9cca53235bd9078b42844e Mon Sep 17 00:00:00 2001 From: Jakit Liang Date: Thu, 22 May 2025 04:40:52 +0800 Subject: [PATCH 2/2] Bugfix when nil object return by `getmetatable` --- script/vm/compiler.lua | 57 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 07762b4c4..c1a722e1f 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -669,6 +669,8 @@ local function getReturnOfGetMetaTable(args) local obj = args[1] local node = vm.createNode() if not obj then + -- 如果没有对象,返回nil类型 + node:merge(vm.declareGlobal('type', 'nil')) return node end @@ -727,14 +729,9 @@ local function getReturnOfGetMetaTable(args) end end - -- 如果没有找到任何元表信息,创建一个空表类型 + -- 如果没有找到任何元表信息,返回nil类型 if not foundMetatable then - local tableObj = { - type = 'table', - start = obj.start or 0, - finish = obj.finish or 0, - } - node:merge(tableObj) + node:merge(vm.declareGlobal('type', 'nil')) end node:remove 'nil' @@ -1795,7 +1792,7 @@ local compilerSwitch = util.switch() end) : case 'tablefield' : case 'tableindex' - : call(function (source) + : call(function (source, lastKey, pushResult) local hasMarkDoc if source.bindDocs then hasMarkDoc = vm.bindDocs(source) @@ -2083,6 +2080,50 @@ local compilerSwitch = util.switch() end) : case 'call' : call(function (source) + -- 检查是否是getmetatable函数引用的方法调用 + if source.node and source.node.type == 'getmethod' and source.args then + local method = source.node + local node = method.node + + -- 检查是否是通过引用调用getmetatable + if node then + local methodName = method.method and method.method[1] + if methodName then + -- 使用compileByParentNode直接查找属性 + vm.compileByParentNode(node, methodName, function (field) + if field.value and field.value.special == 'getmetatable' then + -- 是getmetatable的引用,安全处理 + -- 创建一个新节点避免guide.getRoot错误 + vm.setNode(source, getReturnOfGetMetaTable({node})) + return + end + end) + end + end + end + + -- 检查常规函数调用中直接调用getmetatable的情况 + if source.node and source.node.special == 'getmetatable' then + -- 直接使用getReturnOfGetMetaTable处理 + if source.args and source.args[1] then + vm.setNode(source, getReturnOfGetMetaTable(source.args)) + return + end + end + + -- 检查方法内部直接调用getmetatable(self)的情况 + if source.node and source.node.type == 'getlocal' and source.node[1] == 'getmetatable' then + if source.args and source.args[1] and source.args[1].type == 'getlocal' and source.args[1][1] == 'self' then + -- 获取self对象引用 + local selfNode = guide.getSelfNode(source.args[1]) + if selfNode then + -- 处理元表,但不直接传递可能导致guide.getRoot错误的对象 + vm.setNode(source, getReturnOfGetMetaTable({selfNode})) + return + end + end + end + -- ignore rawset if source.node.special == 'rawset' then return