Skip to content

Commit e2b37b1

Browse files
committed
fix: handle composite generic return types (T[], Wrapper<T>)
Address PR review comment: The previous implementation only handled direct T → string substitution but failed for composite types like T[] or Wrapper<T>. Changes: - Use vm.cloneObject on the actual return annotation (doc.return) instead of iterating through compiled node objects - Extend vm.cloneObject to handle doc.type.name when name matches a generic param (converts to doc.generic.name with _resolved set) - Extend vm.cloneObject to handle doc.type.sign for nested generic types, but only when signs contain doc.type.name needing resolution (preserves function-level generics like Callback<<T>>) Added test cases for: - T[] resolving to string[] - Wrapper<T> resolving to Wrapper<string>
1 parent 962293f commit e2b37b1

3 files changed

Lines changed: 98 additions & 17 deletions

File tree

script/vm/compiler.lua

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,26 +1584,27 @@ local function bindReturnOfFunction(source, mfunc, index, args)
15841584
end
15851585
end
15861586

1587-
if next(genericMap) then
1588-
-- Check the return node for global type references that match generic params
1589-
local newReturnNode = vm.createNode()
1590-
local hasReplacement = false
1591-
for retNode in returnNode:eachObject() do
1592-
if retNode.type == 'global' and retNode.cate == 'type' then
1593-
local resolvedNode = genericMap[retNode.name]
1594-
if resolvedNode then
1595-
newReturnNode:merge(resolvedNode)
1596-
hasReplacement = true
1597-
else
1598-
newReturnNode:merge(retNode)
1587+
if next(genericMap) and mfunc.bindDocs then
1588+
for _, doc in ipairs(mfunc.bindDocs) do
1589+
if doc.type == 'doc.return' then
1590+
for _, rtn in ipairs(doc.returns) do
1591+
if rtn.returnIndex == index then
1592+
local newRtn = vm.cloneObject(rtn, genericMap)
1593+
if newRtn then
1594+
returnNode = vm.compileNode(newRtn)
1595+
for rnode in returnNode:eachObject() do
1596+
if rnode.type == 'generic' then
1597+
returnNode = rnode:resolve(guide.getUri(source), args)
1598+
break
1599+
end
1600+
end
1601+
end
1602+
break
1603+
end
15991604
end
1600-
else
1601-
newReturnNode:merge(retNode)
1605+
break
16021606
end
16031607
end
1604-
if hasReplacement then
1605-
returnNode = newReturnNode
1606-
end
16071608
end
16081609
break
16091610
end

script/vm/generic.lua

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ local function cloneObject(source, resolved)
3434
end
3535
return newName
3636
end
37+
if source.type == 'doc.type.name' then
38+
local key = source[1]
39+
if resolved[key] then
40+
local newName = {
41+
type = 'doc.generic.name',
42+
start = source.start,
43+
finish = source.finish,
44+
parent = source.parent,
45+
[1] = source[1],
46+
}
47+
vm.setNode(newName, resolved[key], true)
48+
newName._resolved = resolved[key]
49+
return newName
50+
end
51+
end
3752
if source.type == 'doc.type' then
3853
local newType = {
3954
type = source.type,
@@ -113,6 +128,36 @@ local function cloneObject(source, resolved)
113128
end
114129
return newDocFunc
115130
end
131+
if source.type == 'doc.type.sign' and source.signs then
132+
local needsClone = false
133+
for _, sign in ipairs(source.signs) do
134+
if sign.type == 'doc.type' then
135+
for _, tp in ipairs(sign.types) do
136+
if tp.type == 'doc.type.name' and resolved[tp[1]] then
137+
needsClone = true
138+
break
139+
end
140+
end
141+
elseif sign.type == 'doc.type.name' and resolved[sign[1]] then
142+
needsClone = true
143+
end
144+
if needsClone then break end
145+
end
146+
if needsClone then
147+
local newSign = {
148+
type = source.type,
149+
start = source.start,
150+
finish = source.finish,
151+
parent = source.parent,
152+
node = source.node,
153+
signs = {},
154+
}
155+
for i, sign in ipairs(source.signs) do
156+
newSign.signs[i] = cloneObject(sign, resolved)
157+
end
158+
return newSign
159+
end
160+
end
116161
return source
117162
end
118163

test/type_inference/common.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5011,3 +5011,38 @@ TEST 'store<string>' [[
50115011
50125012
local <?string_store?> ---@type store<string>
50135013
]]
5014+
5015+
-- Test composite return types with generics (T[] should resolve to string[])
5016+
TEST 'string[]' [[
5017+
---@class Container<T>
5018+
local Container = {}
5019+
5020+
---@return T[]
5021+
function Container:getAll()
5022+
return {}
5023+
end
5024+
5025+
---@type Container<string>
5026+
local c
5027+
5028+
local <?items?> = c:getAll()
5029+
]]
5030+
5031+
-- Test nested generic return types (Wrapper<T> should resolve to Wrapper<string>)
5032+
TEST 'Wrapper<string>' [[
5033+
---@class Wrapper<V>
5034+
---@field value V
5035+
5036+
---@class Factory<T>
5037+
local Factory = {}
5038+
5039+
---@return Wrapper<T>
5040+
function Factory:wrap()
5041+
return {}
5042+
end
5043+
5044+
---@type Factory<string>
5045+
local f
5046+
5047+
local <?wrapped?> = f:wrap()
5048+
]]

0 commit comments

Comments
 (0)