Skip to content

[Feature request] generics in overload #723

New issue

Have a question about this project? No Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “No Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? No Sign in to your account

Open
lua-rocks opened this issue Oct 10, 2021 · 7 comments
Open

[Feature request] generics in overload #723

lua-rocks opened this issue Oct 10, 2021 · 7 comments
Labels
enhancement New feature or request feat/generic Related to generic emulation feature feat/LuaCats Annotations Related to Lua Language Server Annotations (LuaCats)

Comments

@lua-rocks
Copy link

lua-rocks commented Oct 10, 2021

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 🤯

@sumneko sumneko added feat/LuaCats Annotations Related to Lua Language Server Annotations (LuaCats) enhancement New feature or request labels Oct 11, 2021
@Miqueas
Copy link

Miqueas commented Oct 13, 2021

Interesting... Aside of the EmmyLua annotation, how do you achieve overloading in Lua? 👀

@lua-rocks
Copy link
Author

@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.

@Miqueas
Copy link

Miqueas commented Oct 14, 2021

@lua-rocks awesome, thanks!

@carsakiller carsakiller added the feat/generic Related to generic emulation feature label Jan 30, 2023
@Mayron
Copy link

Mayron commented Apr 12, 2023

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.

@distantforest1
Copy link

distantforest1 commented Aug 22, 2023

Just want to add my +1 to this as well. Just ran into this issue. Would be great to have.

@ZSaberLv0
Copy link

+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`

@tomlau10
Copy link
Contributor

tomlau10 commented Oct 13, 2024

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 @param and @return annotations. 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

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:
    -- clear node caches of args to allow recomputation with the type narrowed call
    for _, arg in ipairs(call.args) do
    vm.setNode(arg, vm.createNode(), true)
    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 n.args[i] then
    vm.setNode(arg, vm.compileNode(n.args[i]))
    end
    end
    end
    end
  • => 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 🤔

No Sign up for free to join this conversation on GitHub. Already have an account? No Sign in to comment
Labels
enhancement New feature or request feat/generic Related to generic emulation feature feat/LuaCats Annotations Related to Lua Language Server Annotations (LuaCats)
Projects
None yet
Development

No branches or pull requests

8 participants