Class generics produce unusable and wrong union types
How are you using the lua-language-server?
Visual Studio Code Extension (sumneko.lua)
Which OS are you using?
MacOS, Windows
What is the issue affecting?
Completion
Expected Behaviour
Methods Peek,Push,Pop have proper type hints
Actual Behaviour
While types of a, b, c are correct the
type hints of methods are unusable with unnecessary union types.
If Push is removed from superclass, then type hints go to normal without unions but still with <T> in many places
local _lua_setmetatable = setmetatable
Stack = {}
--- @generic T
--- @class Stack<T> : {
--- Pop: (fun(self: Stack<T>): T),
--- Peek: (fun(self: Stack<T>): T),
--- Push: (fun(self: Stack<T>, value: T): Stack<T>),
--- }
--- @field protected _stack any[]
--- @field protected _count integer
Stack.__index = {}
--- Creates a new, empty stack.
--- @generic T
--- @return Stack<T>
function Stack.New()
local this = {
_stack = {},
_count = 0,
}
return _lua_setmetatable(this, Stack)
end
local st = Stack.New() --[[@as Stack<{ num: integer }>]]
local a = st:Peek()
local b = st:Pop()
local c = st:Push({})
Reproduction steps
Analyze specified code in VSCode with luals installed
Additional Notes
No response
Log File
No response
AFAIK, the generic support of LuaLS is very limited. 😇 There are many caveats, and the "best" annotation that I attempted to achieve your needs is as follow:
---@class Stack<T>: { __hidden: T }
---@field protected _stack any[]
---@field protected _count integer
Stack = {}
Stack.__index = Stack
---@return Stack
function Stack.New()
local self = {
_stack = {},
_count = 0,
}
return setmetatable(self, Stack)
end
---@generic T
---@param self Stack<T>
---@return T
function Stack:Pop() return nil end
---@generic T
---@param self Stack<T>
---@return T
function Stack:Peek() return nil end
---@generic self, T
---@param self self|Stack<T>
---@param value T
---@return self
function Stack:Push(value) return self end
--- demo
---@type Stack<{ num: integer }>
local st = Stack.New()
local a = st:Peek() --> { num: integer }
local b = st:Pop() --> { num: integer }
local c = st:Push({}) --> Stack<{ num: integer }>
caveat 1
A generic class must inherit a table with a field to store the generic type T
ref: https://github.com/LuaLS/lua-language-server/issues/1532#issuecomment-2542346524
In my example you can see there is a { __hidden: T } there.
If you remove it, then ---@param self Stack<T> annotation in the methods won't work
=> which will cause :Peek() and :Pop() annotation failing to work
caveat 2
A generic type in function annotation can only do type capturing but not type enforcing
- i.e. the
---@param value TinStack:Pushactually does nothing - because it can only be used the match the type
TinStack<T> - but cannot be used the enforce
valueparam to be of the captured typeT - and therefore LuaLS cannot check against incorrect
valuetype that you pass to:Push()
If you use generic types heavily, you may want to try CppCXY's new language server written in Rust. It has much better generic class support 👀
- https://github.com/LuaLS/lua-language-server/issues/3017
- https://github.com/LuaLS/lua-language-server/issues/3017#issuecomment-2876633965