Skip to content

Commit 7843eec

Browse files
committed
Fix method-level generic return type being overwritten by receiver class generic resolution
When a generic class method has a @return type referencing method-level generics (e.g. @return R where R is not in the class genericMap), the receiver self-generic block would clone the return doc using only the class genericMap, leaving method-level generics unresolved. This overwrote Round 1's correct resolution. Fix: scout before clone — scan the return doc for doc.generic.name nodes not present in genericMap, and only proceed with the receiver block if all referenced generics are class-level. Discussion: #3438
1 parent a9aa09a commit 7843eec

3 files changed

Lines changed: 39 additions & 7 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* `FIX` Deduplicate documentation bindings for parameters
77
* `FIX` Correct `math.type` meta return annotation to use `nil` instead of the string literal `'nil'`
88
* `FIX` Fix initial `nameStyle.config` not getting loaded in the appropriate workspace.
9+
* `FIX` Fix method-level generic return type being overwritten by receiver class generic resolution [#3438](https://github.com/LuaLS/lua-language-server/discussions/3438)
910

1011
## 3.18.2
1112
* `CHG` `duplicate-set-field` diagnostic now supports linked suppression: when any occurrence of a duplicate field is suppressed with `---@diagnostic disable` or `---@diagnostic disable-next-line`, all warnings for that field name will be suppressed

script/vm/compiler.lua

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,13 +1752,25 @@ local function bindReturnOfFunction(source, mfunc, index, args)
17521752
if doc.type == 'doc.return' then
17531753
for _, rtn in ipairs(doc.returns) do
17541754
if rtn.returnIndex == index then
1755-
local newRtn = vm.cloneObject(rtn, genericMap)
1756-
if newRtn then
1757-
returnNode = vm.compileNode(newRtn)
1758-
for rnode in returnNode:eachObject() do
1759-
if rnode.type == 'generic' then
1760-
returnNode = rnode:resolve(guide.getUri(source), args)
1761-
break
1755+
-- Only clone if the return type only references class-level generics
1756+
-- (i.e. generics that exist in the genericMap from the receiver).
1757+
-- Method-level generics (e.g. V not in {T=string}) are
1758+
-- already resolved correctly in the first round above.
1759+
local onlyClassGenerics = true
1760+
guide.eachSourceType(rtn, 'doc.generic.name', function(src)
1761+
if not genericMap[src[1]] then
1762+
onlyClassGenerics = false
1763+
end
1764+
end)
1765+
if onlyClassGenerics then
1766+
local newRtn = vm.cloneObject(rtn, genericMap)
1767+
if newRtn then
1768+
returnNode = vm.compileNode(newRtn)
1769+
for rnode in returnNode:eachObject() do
1770+
if rnode.type == 'generic' then
1771+
returnNode = rnode:resolve(guide.getUri(source), args)
1772+
break
1773+
end
17621774
end
17631775
end
17641776
end

test/type_inference/common.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5215,3 +5215,22 @@ local mylist
52155215
52165216
local <?result?> = mylist:identity()
52175217
]]
5218+
5219+
-- Discussion #3438: Method-level generic return type not clobbered by receiver block
5220+
-- When a generic class method has @return R where R is method-only (not in class genericMap),
5221+
-- the receiver block should not overwrite Round 1's correct resolution.
5222+
TEST 'integer' [[
5223+
---@generic T
5224+
---@class MyClass<T>
5225+
local MyClass = {}
5226+
5227+
---@generic R
5228+
---@param val R
5229+
---@return R
5230+
function MyClass:pass(val) return val end
5231+
5232+
---@type MyClass<string>
5233+
local myClass
5234+
5235+
local <?result?> = myClass:pass(42)
5236+
]]

0 commit comments

Comments
 (0)