You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Best way to implement something like an abstract base class? #2453
I'm working on a framework where users of it implement a kind of module that mixes their provided logic with some optional event handlers.
Here is a simple example
-- provided by framework
---@class Base
---@field OnClick fun(self: Base, x: number, y: number)
local base = {}
---@generic T: Base
---@param name `T`
---@return T
function New(name)
return setmetatable({}, {__index = base})
end
-- usage of framework by user
---@class MyImpl: Base
local foo = New("MyImpl")
function foo:OnClick(x, y) -- x and y are inferred as any instead of number :(
end
function foo:myHelperFunction()
end
So I expect the user to implement OnClick and fill it with their logic. My goal here is that the user automatically gets the correct types on the parameters for foo:OnClick. Here in this simple example it's just two numbers, but my real app has like > 10 such methods and some of these take complex types as parameters.
With the code above it doesn't know anything about the parameters in OnClick and shows them as any. Maybe I got something wrong with the generics, but even without all the fancy generic stuff (just @return base on New) it's the same problem.
The only work-around I found was to not use @class on the user-defined class and not define a generic, this makes the parameters on the OnCLick function definition correct! However, it now triggers injected-field on all user-defined values not defined in the base class like the myHelperFunction method :(
No you didn't get anything wrong afaik. I wish this was supported too.
However, it now triggers injected-field on all user-defined values not defined in the base class like the myHelperFunction method :(
You (or the downstream user) could always just manually ignore those errors I suppose (using ---@diagnostic), and I think those funcs/methods would still get applied to the derived class type.
Also, FWIW, there's this lol:
---@classMyImpl:Baselocalfoo=New("MyImpl")
;(--[[@as Base]]foo).OnClick=function(self, x, y)
endfunctionfoo:myHelperFunction()
end
...but that would have to be done manually for each overridden method, and this LSP doesn't necessarily warn you or make it obvious when you are doing so (overriding).
Another hack that might work (in some sense?):
localfoo=New {
OnClick=function(self, x, y)
end,
myHelperFunction=function()
end
}
but then users would be required to call New() like this every single time...
Hopefully this gets fixed or improved if/when the LSP is refactored (#2366).
Activity
tmillr commentedon Jan 29, 2024
No you didn't get anything wrong afaik. I wish this was supported too.
You (or the downstream user) could always just manually ignore those errors I suppose (using
---@diagnostic), and I think those funcs/methods would still get applied to the derived class type.Also, FWIW, there's this lol:
...but that would have to be done manually for each overridden method, and this LSP doesn't necessarily warn you or make it obvious when you are doing so (overriding).
Another hack that might work (in some sense?):
but then users would be required to call
New()like this every single time...Hopefully this gets fixed or improved if/when the LSP is refactored (#2366).
emmericp commentedon Jan 30, 2024
FWIW I implemented this as a plugin for our stuff that just injects a @Class annotation together with a lot of @field annotations: https://github.com/DeadlyBossMods/LuaLS-Config/blob/main/DBM-Plugin.lua
bavalpey commentedon Feb 12, 2024
I'm looking through your solution, @emmericp and I can't wrap my head around what exactly you've done to accomplish this.