lua-language-server
lua-language-server copied to clipboard
[Feature request] generics in overload
As far as I know, it is not possible to use @generic in @overload?
I have a table Object that has a __call metamethod, so class Object can be used as function: Object() == Object:new()
... And I want to overload class Object with function new(). This function use generics 🤯
Interesting... Aside of the EmmyLua annotation, how do you achieve overloading in Lua? :eyes:
@Miqueas Basic overloading is achieved by checking the types of function arguments and returning a value according to the data obtained:
---@overload fun(v1:number, v2:number):number
---@overload fun(v1:string):string
local function demo(...)
local args = {...}
if #args == 2
and type(args[1]) == 'number'
and type(args[2]) == 'number' then
return args[1] + args[2]
elseif #args == 1
and type(args[1]) == 'string' then
return args[1]:upper()
end
error('wrong arguments', 2)
end
assert(demo(2, 2) == 4)
assert(demo('hello') == 'HELLO')
A more complex version is demonstrated here.
@lua-rocks awesome, thanks!
Just discovered this problem as well. Looking forward to being able to use both of these together. I don't think any additional annotation should be required. Overload just needs to accept the already specified generic annotation.
Just want to add my +1 to this as well. Just ran into this issue. Would be great to have.
+1 waiting for the generic for overload, and here is my use case:
---@meta xxx
---@class PropType
PropType = ...
---@overload fun():PropType
function Base:baseProp() ... end
---@generic T:Base
---@param self T
---@overload fun(v:PropType):T
function Base:baseProp(v) ... end
---@class Child:Base
...
function Child:childProp(v) ... end
usage:
local obj = Child()
obj
:baseProp(xxx) -- <= chained call for setter to return `self`, and return `Child` here by `generic`
:childProp(xxx)
local v = obj:baseProp() -- <= getter and setter with same name, by `overload`
Hi @ZSaberLv0, if you are writing a @meta file, you generally don't need @overload, because you can just define each function as detailed as it should be. 😄 This is the recommended way in the luals wiki: https://luals.github.io/wiki/annotations/#overload
Note If you are writing definition files, it is recommended to instead write multiple function definitions, one for each needed signature with its
@paramand@returnannotations. This allows the functions to be as detailed as possible. Because the functions do not exist at runtime, this is acceptable.
So in your case you can have something like this:
---@meta
---@class PropType
PropType = {}
---@class Base
---@overload fun(): Base
Base = {}
---@return PropType
function Base:baseProp() end
---@generic T
---@param self T
---@param v PropType
---@return T
function Base:baseProp(v) end
---@class Child: Base
---@overload fun(): Child
Child = {}
function Child:childProp(v) end
However I don't know why the above doesn't work 😕 it returns unknown after obj:baseProb()
local obj = Child()
local r = obj:baseProp(xxx) --> r: unknown
Only if I remove the function Base:baseProp() end definition, then luals can infer r: Child
The above is tested in v3.11.1, and when I rolled back to v3.9.3, it works ‼️
So I guess some bugs are introduced in some recent changes 🤔 and more debuggings have to be done to identify this regression issue.
edit
- After some more testing, my code snippet above works until
v3.10.0, and stopped working atv3.10.1. - As far as I know,
v3.10.1is just fixing a runtime error bug introduced by my #2765 (which is released inv3.10.0) - maybe that fix in
v3.10.1has some undesired side effect and is causing this regression 😨
edit2
I think I have found a fix for this regression issue. Although I don't fully understand how the fix works, at least after patching then my above code snippet works again 😂 I will open a PR for it. The patch:
- change the following: https://github.com/LuaLS/lua-language-server/blob/db667f6db7ea6852d38460a1ed046eb85bb9e5ff/script/vm/compiler.lua#L633-L646
- => to
-- clear node caches of args to allow recomputation with the type narrowed call
for _, arg in ipairs(call.args) do
if vm.getNode(arg) then
vm.setNode(arg, vm.createNode(), true)
end
end
for n in newNode:eachObject() do
if n.type == 'function'
or n.type == 'doc.type.function' then
for i, arg in ipairs(call.args) do
if vm.getNode(arg) and n.args[i] then
vm.setNode(arg, vm.compileNode(n.args[i]))
end
end
end
end
- in short, only clear existing node caches. If the cache doesn't exist before, we should not set an empty node for it 🤔