[feat] generics: capture types with backticks - #3149
Conversation
|
I think there is a big problem here: 😕
just using your example above ---@type Vehicle|nil
local v1 = {}
local r1 = getItemColor(v1) --> r1: VehicleColor|nilColorPS: I am not maintainer of LuaLS, just one of the contributors. |
|
No problem, I will review that case and update the PR |
|
Hello @tomlau10 . I looked at this after your feeback. I would leave the PR as-is. This indeed can end up creating weird types, but when you are using string you can also do weird stuff, namely, you can call I have another variant of the code that works as follows: ---@type Vehicle|nil
local v1 = {}
local r1 = getItemColor(v1) --> r1: VehicleColor|nilMeaning, Let me know which one would work best. I am fine either way. If not, what would you expect in such cases? My use case is, I am writing Lua bindings for a major ECS, we need a syntax as follows: ---@class transform.Position : Component
---@class transform.PositionInstance
---@field x number
---@field y number
-- Retrieves the provided component value or nil if the component is not found
---@generic T
---@param component `T`Instance # component to retrieve
---@return T | nil
function entity:get(component) end
local pos = entity:get(transform.Position) --> pos: transform.PositionInstance|nil
if pos then
print("Position: ", pos.x, pos.y)
else
print("Entity does not have Position")
endPlease let me know your thoughts. |
When using literal string, I don't think anyone would write Moreover, the current implementation would give inconsistent result in some of the following cases: -- 1. just a variable with no annotation
local v1 --> v1: unknown
local r1 = getItemColor(v1) --> unknown, expected, but out of my expectation that it works correctly
-- 2. a table value
local v2 = {} --> v2: table
local r2 = getItemColor(v2) --> unknown, expected, again out of my expectation that it works correctly
-- 3. nil from function
local function getnil() end
local v3 = getnil() --> v3: nil
local r3 = getItemColor(v3) --> nilColor, unexpected I think
-- 4. integer const from function
local function get1() return 1 end
local v4 = get1() --> v4: integer = 1
local r4 = getItemColor(v4) --> unknown, but I don't know what should be the behavior
-- 5. integer from function
---@return integer
local function getint() return 1 end
local v5 = getint() --> v5: integer
local r5 = getItemColor(v5) --> integerColor, seems inconsistent with const integer case
-- 6. any from function
---@return any
local function getany() return end
local v6 = getany()
local r6 = getItemColor(v6) --> anyColor
I personally think skipping Again I am not author of LuaLS, so this may need opinions from maintainers. 🤔 |
Maybe we could get something like: ---@generic T
---@param x `T`
---@return `T`Colour
local function f(x)
return ...
end |
|
Since there is a And if |
|
I believe that too many peculiar syntaxes have been added to sumneko for various workarounds. This is neither consistent nor does it add cognitive burden. I think a better syntax would be to use built-in special generic classes based on supporting generics. ---@generic T
---@param a T
---@return CompositeType<T, "CCC">
function f(a)
endOf course, this is just a suggestion. I understand that implementing this in LuaLS might be somewhat difficult. |
|
If there is no exact planning on the syntax of how
|
I agree this is the syntax that would make sense, but it is not how the backticks capture works right now unfortunately. @CppCXY @tomlau10 So I agree with @tomlau10 :
... but adding the extra that ---@class transform.Position : Component
---@class transform.PositionInstance
---@field x number
---@field y number
-- Retrieves the provided component value or nil if the component is not found
---@generic T
---@param component `T`Instance # component to retrieve
---@return T | nil # T is actually `T`Instance
function entity:get(component) end
local pos = entity:get(transform.Position) --> pos: transform.PositionInstance|nil
if pos then
print("Position: ", pos.x, pos.y)
else
print("Entity does not have Position")
end |
Yes, that's what I mean 😄
|
This PR modifies the generics capture system to allow to capture types out of the type of the parameter passed, in addition to being able to pass the type as a string which is the way it currently works:
Currently:
Now, the following is also possible:
Therefore, it is possible to pass a literal string that happens to be a type name like before, or otherwise pass an expression that evaluates to a type and that type will be captured with backticks. This allows to construct typings for functions that return different types that depend on the type of the object passed.