Skip to content

Commit 6ccb419

Browse files
committed
fix: undefined-doc-name issue
1 parent a8b4f6f commit 6ccb419

4 files changed

Lines changed: 85 additions & 17 deletions

File tree

script/core/diagnostics/param-type-mismatch.lua

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ local vm = require 'vm'
55
local await = require 'await'
66

77
---@param defNode vm.node
8-
local function expandGenerics(defNode)
8+
---@param classGenericMap table<string, vm.node>?
9+
local function expandGenerics(defNode, classGenericMap)
910
---@type parser.object[]
1011
local generics = {}
1112
for dn in defNode:eachObject() do
@@ -20,27 +21,78 @@ local function expandGenerics(defNode)
2021
end
2122

2223
for _, generic in ipairs(generics) do
23-
local limits = generic.generic and generic.generic.extends
24-
if limits then
25-
defNode:merge(vm.compileNode(limits))
24+
-- First check if this generic is a class generic that can be resolved
25+
local genericName = generic[1]
26+
if classGenericMap and genericName and classGenericMap[genericName] then
27+
defNode:merge(classGenericMap[genericName])
2628
else
27-
local unknownType = vm.declareGlobal('type', 'unknown')
28-
defNode:merge(unknownType)
29+
-- Fall back to constraint or unknown
30+
local limits = generic.generic and generic.generic.extends
31+
if limits then
32+
defNode:merge(vm.compileNode(limits))
33+
else
34+
local unknownType = vm.declareGlobal('type', 'unknown')
35+
defNode:merge(unknownType)
36+
end
37+
end
38+
end
39+
end
40+
41+
---@param uri uri
42+
---@param source parser.object
43+
---@return table<string, vm.node>?
44+
local function getReceiverGenericMap(uri, source)
45+
local callNode = source.node
46+
if not callNode then
47+
return nil
48+
end
49+
-- Only resolve generics for method calls (obj:method()), not static calls (Class.method())
50+
if callNode.type ~= 'getmethod' then
51+
return nil
52+
end
53+
local receiver = callNode.node
54+
if not receiver then
55+
return nil
56+
end
57+
local receiverNode = vm.compileNode(receiver)
58+
for rn in receiverNode:eachObject() do
59+
if rn.type == 'doc.type.sign' and rn.signs and rn.node and rn.node[1] then
60+
local classGlobal = vm.getGlobal('type', rn.node[1])
61+
if classGlobal then
62+
return vm.getClassGenericMap(uri, classGlobal, rn.signs)
63+
end
2964
end
3065
end
66+
return nil
3167
end
3268

3369
---@param funcNode vm.node
3470
---@param i integer
71+
---@param classGenericMap table<string, vm.node>?
3572
---@return vm.node?
36-
local function getDefNode(funcNode, i)
73+
local function getDefNode(funcNode, i, classGenericMap)
3774
local defNode = vm.createNode()
3875
for src in funcNode:eachObject() do
3976
if src.type == 'function'
4077
or src.type == 'doc.type.function' then
4178
local param = src.args and src.args[i]
4279
if param then
43-
defNode:merge(vm.compileNode(param))
80+
local paramNode = vm.compileNode(param)
81+
-- Check for global type references that match class generic params
82+
if classGenericMap then
83+
local newNode = vm.createNode()
84+
for pn in paramNode:eachObject() do
85+
if pn.type == 'global' and pn.cate == 'type' and classGenericMap[pn.name] then
86+
-- Replace the global type reference with the resolved type
87+
newNode:merge(classGenericMap[pn.name])
88+
else
89+
newNode:merge(pn)
90+
end
91+
end
92+
defNode:merge(newNode)
93+
else
94+
defNode:merge(paramNode)
95+
end
4496
if param[1] == '...' then
4597
defNode:addOptional()
4698
end
@@ -51,7 +103,7 @@ local function getDefNode(funcNode, i)
51103
return nil
52104
end
53105

54-
expandGenerics(defNode)
106+
expandGenerics(defNode, classGenericMap)
55107

56108
return defNode
57109
end
@@ -87,12 +139,14 @@ return function (uri, callback)
87139
end
88140
await.delay()
89141
local funcNode = vm.compileNode(source.node)
142+
-- Get the class generic map for method calls on generic class instances
143+
local classGenericMap = getReceiverGenericMap(uri, source)
90144
for i, arg in ipairs(source.args) do
91145
local refNode = vm.compileNode(arg)
92146
if not refNode then
93147
goto CONTINUE
94148
end
95-
local defNode = getDefNode(funcNode, i)
149+
local defNode = getDefNode(funcNode, i, classGenericMap)
96150
if not defNode then
97151
goto CONTINUE
98152
end

script/core/diagnostics/undefined-doc-name.lua

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,22 @@ local function isClassGenericParam(source, name, uri)
5454
end
5555

5656
-- Check if bound to a method on a generic class
57-
-- First, find the function from any doc in the bindGroup
57+
-- Find the function from any doc in the bindGroup
5858
local func = nil
5959
if bindGroup then
6060
for _, other in ipairs(bindGroup) do
6161
local bindSource = other.bindSource
6262
if bindSource then
6363
if bindSource.type == 'function' then
64+
-- doc.return binds directly to function
6465
func = bindSource
6566
break
66-
elseif bindSource.parent and bindSource.parent.type == 'function' then
67-
func = bindSource.parent
68-
break
67+
else
68+
-- doc.param binds to local param, find containing function
69+
func = guide.getParentFunction(bindSource)
70+
if func then
71+
break
72+
end
6973
end
7074
end
7175
end

script/vm/compiler.lua

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,12 @@ local function containsGenericName(obj)
280280
return false
281281
end
282282

283+
---Builds a map from generic parameter names to their concrete types
283284
---@param uri uri
284285
---@param classGlobal vm.global
285286
---@param signs parser.object[]
286287
---@return table<string, vm.node>?
287-
local function getClassGenericMap(uri, classGlobal, signs)
288+
function vm.getClassGenericMap(uri, classGlobal, signs)
288289
for _, set in ipairs(classGlobal:getSets(uri)) do
289290
if set.type == 'doc.class' and set.signs then
290291
local resolved = {}
@@ -315,7 +316,7 @@ local function resolveGenericField(uri, classGlobal, field, signs)
315316
if not containsGenericName(field.extends) then
316317
return nil
317318
end
318-
local resolved = getClassGenericMap(uri, classGlobal, signs)
319+
local resolved = vm.getClassGenericMap(uri, classGlobal, signs)
319320
if not resolved then
320321
return nil
321322
end
@@ -1724,7 +1725,7 @@ local function bindReturnOfFunction(source, mfunc, index, args)
17241725
if rn.type == 'doc.type.sign' and rn.signs and rn.node and rn.node[1] then
17251726
local classGlobal = vm.getGlobal('type', rn.node[1])
17261727
if classGlobal then
1727-
local genericMap = getClassGenericMap(guide.getUri(source), classGlobal, rn.signs)
1728+
local genericMap = vm.getClassGenericMap(guide.getUri(source), classGlobal, rn.signs)
17281729
if genericMap and mfunc.bindDocs then
17291730
for _, doc in ipairs(mfunc.bindDocs) do
17301731
if doc.type == 'doc.return' then

test/diagnostics/undefined-doc-name.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ function Map:get(key)
4646
end
4747
]]
4848

49+
-- Variable name different from class name
50+
TEST [[
51+
---@class Pool<T>
52+
local M = {}
53+
54+
---@param item T
55+
function M:push(item) end
56+
]]
57+
4958
-- Undefined types SHOULD still warn (control case)
5059
TEST [[
5160
---@class Container<T>

0 commit comments

Comments
 (0)