镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Generics Super Issue #1861

Open
Open

Activity

  1. pinned this issue on Jan 30, 2023
  2. bandaloo commented on Jan 31, 2023

    @bandaloo

    thanks for putting all these in one place. i found some more edge cases; maybe some of them are listed here already but i'll try to document them more diligently.

  3. jakitliang commented on Dec 2, 2023

    @jakitliang

    When will it get done?

  4. tmillr commented on Jan 29, 2024

    @tmillr
    Contributor

    Here's another issue with generics that I don't believe has been documented yet?

    ---@class A
    ---@field k string
    
    ---@generic T: A
    ---@param p1 T
    function f(p1) local m = p1.k end

    ^^^ here p1 should have a type of A within the function, but it doesn't, therefore m receives the type unknown

  5. checkraisefold commented on Jun 27, 2024

    @checkraisefold

    Here's another issue with generics that I don't believe has been documented yet?

    ---@class A
    ---@field k string
    
    ---@generic T: A
    ---@param p1 T
    function f(p1) local m = p1.k end

    ^^^ here p1 should have a type of A within the function, but it doesn't, so m receives the type unknown

    This is odd since this use case works but you have to use the parameter capture for the generic to get its type and it also only works with base Lua types and not custom classes
    see #2355 description

  6. tmillr commented on Jul 10, 2024

    @tmillr
    Contributor

    Here's another issue with generics that I don't believe has been documented yet?

    ---@class A
    ---@field k string
    
    ---@generic T: A
    ---@param p1 T
    function f(p1) local m = p1.k end

    ^^^ here p1 should have a type of A within the function, but it doesn't, so m receives the type unknown

    This is odd since this use case works but you have to use the parameter capture for the generic to get its type and it also only works with base Lua types and not custom classes see #2355 description

    I wouldn't really consider that to be "working". Passing strings at runtime just to get the LSP to work correctly is awkward. In theory this should be an easy fix, as the concrete type is literally right there and apart of the function's signature. It's not much different than getting/substituting the type of a concrete @param, there's just an extra layer of indirection (i.e. p1 -> T -> A instead of p1 -> T).

  7. checkraisefold commented on Jul 10, 2024

    @checkraisefold

    Here's another issue with generics that I don't believe has been documented yet?

    ---@class A
    ---@field k string
    
    ---@generic T: A
    ---@param p1 T
    function f(p1) local m = p1.k end

    ^^^ here p1 should have a type of A within the function, but it doesn't, so m receives the type unknown

    This is odd since this use case works but you have to use the parameter capture for the generic to get its type and it also only works with base Lua types and not custom classes see #2355 description

    I wouldn't really consider that to be "working". Passing strings at runtime just to get the LSP to work correctly is awkward. In theory this should be an easy fix, as the concrete type is literally right there and apart of the function's signature. It's not much different than getting/substituting the type of a concrete @param, there's just an extra layer of indirection (i.e. p1 -> T -> A instead of p1 -> T).

    Oh yeah, definitely not "working" per se but very odd that it works with strings and only base Lua types. I would think it would literally be easier to implement the other way code wise

  8. AserJoker commented on Sep 18, 2024

    @AserJoker

    how to implement like

    //  typescript interface
    interface A<T>{
    next<K>(callback:(value:T)=>K):A<K>;
    };

    or not support now

  9. CelDaemon commented on Dec 29, 2024

    @CelDaemon

    Is it planned to have vararg generics?
    The current type definitions for assert(v, message, ...) can only return the type of the first argument, but not of the varargs that are returned.

  10. CelDaemon commented on Jan 1, 2025

    @CelDaemon

    It would also be nice to get something like this working:

    --- Calls function `f` with the given arguments in protected mode with a new message handler.
    --- 
    --- @generic Ta
    --- @generic Tr
    --- 
    --- @param f async fun(...: Ta): ...: Tr The function to call.
    --- @param msgh fun(error: string) The error handler.
    --- @param ... Ta Arguments to call the function with.
    --- @return Tr ...
    function xpcall(f, msgh, ...) end

    (A few things are wrong, but this is the idea)

    Another place where this could be useful is unpack(list, i, j) function, of which even the annotations do not work correctly at this time.

  11. brunoti commented on Feb 10, 2025

    @brunoti

    how to implement like

    // typescript interface
    interface A{
    next(callback:(value:T)=>K):A;
    };
    or not support now

    anyone has made this work?

  12. aiya000 commented on Oct 11, 2025

    @aiya000

    Are there any issues regarding scoped generics?
    I'm interested in this :)

    This is a major feature in TypeScript and Haskell (GHC extensions).

    ---@generic T
    ---@param x T
    local function f(x)
      ---@type T
      local y = some_any_type_value
    end

    This is a useful example of scoped generics, and there are many other useful applications.

  13. litoj commented on Nov 9, 2025

    @litoj

    I noticed a surprising behaviour regarding generic type reuse/extraction from a generic class to a generic function:

    ---@class BasicOpts {test:string}
    ---@class Preset<T> T|{preset:string}
    
    ---@generic T: BasicOpts
    ---@param preset Preset<T>
    ---@return boolean
    local function hasValue(preset)
      return preset.test ~= nil
    end

    This code shows warnings for accessing a non-existent field (.test). When I requested the type info of preset I got:

    (parameter) preset: Preset<<T:BasicOpts>>

    I have no idea what it is representing inside the lsp, but I do know that there is an extra pair of surrounding <>

  14. nullromo commented on Nov 10, 2025

    @nullromo

    Just wanted to note that this description from the docs needs a little more detail:

    Surrounding the generic in backticks (`) will capture the value and use it for the type.

    What does capture mean? What's the difference between T and `T`? It would help if the given example had a use with and without the backticks and explained what the difference is.

  15. litoj commented on Nov 11, 2025

    @litoj

    What does capture mean? What's the difference between T and `T`? It would help if the given example had a use with and without the backticks and explained what the difference is.

    This came useful for me when I had a function receiving a generic parameter that I also wanted to be able to return. In other words the type capture means that you're telling the lsp to use that usage of the generics to determine its type (when referenced in other annotations as well):

    ---Resolve option inheritance etc. and set automatic enabler tables index fallbacks
    ---@generic T: manipulator.ts.MethodOpts
    ---@generic S: manipulator.ts.MethodOpts
    ---@param orig `S`
    ---@param opts? manipulator.ts.PresetOpts<`T`>
    ---@return S|T
    local function extend_opts(orig, opts)
      return opts.preset and M.presets[opts.preset] or orig
    end
  16. nullromo commented on Nov 11, 2025

    @nullromo

    @litoj I'm sorry but I still don't understand. It needs a side-by-side comparison. For instance, what is the difference between these two functions?

    ---@generic T
    ---@param arg T
    local foo = function(arg)
        -- type hint is: function foo(arg: <T>)
    end
    
    ---@generic T
    ---@param arg `T`
    local foo2 = function(arg)
        -- type hint is: function foo(arg: <T>)
    end

    The type hint for both of these is just function foo(arg: <T>). I've been trying various combinations of multiple args, multiple generics, different return types, etc. and I still don't understand the difference.

    Maybe my example is not a good once since they are the same in this case (?) but could you provide a similar simple example that illustrates the difference?


    In your example, the function type hint is

    function extend_opts(orig: <S>, opts?: manipulator.ts.PresetOpts<<T>>)
      -> <S:manipulator.ts.MethodOpts>|<T:manipulator.ts.MethodOpts>

    But if I remove the `s from your example, the type hint becomes

    function extend_opts(orig: <S:manipulator.ts.MethodOpts>, opts?: manipulator.ts.PresetOpts<<T:manipulator.ts.MethodOpts>>)
      -> <S:manipulator.ts.MethodOpts>|<T:manipulator.ts.MethodOpts>

    So as far as I can tell, all the `s did was just remove some of the type information from the function signature. Without the `s, the function maintains the narrower type annotations for the args, but with the `s it just discards the parent type part. Is that the idea?

  17. tomlau10 commented on Nov 12, 2025

    @tomlau10
    Contributor

    @nullromo I am not sure if @litoj 's example is a correct usage of backtick capture in LuaLS 😕

    AFAIK, backtick capture basically means capture the literal string param value (at the caller side) as the type name.
    Then you can use this type name in @return.
    It's NOT about the type hint of your function, nor how you can use this T inside your function implementation.

    example

    ---@generic T
    ---@param arg T
    ---@return T
    local foo1 = function(arg) return {} end
    local r1 = foo1("Test") --> r1: string
    -- because the type of "test" is string, so "T = string"
    
    ---@generic T
    ---@param arg `T`
    ---@return T
    local foo2 = function(arg) return {} end
    local r2 = foo2("Test") --> r2: Test
    -- because the the string value "Test" is converted to type name, so "T = Test`
    -- of course this requires you to have a `@class Test` first, otherwise no further completions for this `r2: Test`

    more advanced usage

    There is another more advanced usage, where you can combine this captured type name with other string
    => to form another combined type name

    ---@generic T
    ---@param arg Object.`T`
    ---@return T
    local foo3 = function(arg) return {} end
    local r3 = foo3("Test") --> r3: Object.Test
    -- the "Test" string is captured, and then combined with `Object.` to make `T = Object.Test`
    -- then finally return this `Object.Test` to caller

    There is also a related PR #3149 which enhances this capture feature, to support capturing the actually param type as type name.
    But seems maintainer don't agree with this new behavior and the PR is not merged for a long time 🙈

  18. nullromo commented on Nov 12, 2025

    @nullromo

    @tomlau10 Thank you for the explanation. This makes it very clear what the backticks are doing. I think part of the difficulty I had in understanding it is that I didn't see an immediate practical use of this feature. Why would I write a function where I have to tell it what type to return using a parameter? All I can think of is that Lua doesn't have true generics, so it would be something like this

    /* typescript code */
    const foo = <T>(arg1, arg2): T => {
        // T is not actually accessible at all because it's a real generic
    }
    const r = foo<number>(x, y);

    In Lua, we can't give <T> to the function, so we just pass it as an argument and don't actually use it.

    ---@generic T
    ---@param __returnType `T`
    ---@return T
    local foo = function(__returnType, arg1, arg2)
        -- do not actually need to use __returnType here, just use arg1 and arg2
    end
    local r = foo('number', x, y)

    This is my guess as to why this feature exists. Is this accurate?


    more advanced usage

    For this one, I see what it's doing but the syntax is a little confusing. Based on ---@generic T and ---@return T, I would expect the return type to still be T. I would expect something like ---@return Object.`T` to be required to get Object.Test as the reutrn type. But whatever, it makes sense enough as-is. And again, thank you for explaining!


    I will look and see if I can make a PR that updates the wiki slightly to make the example a bit more clear while still being concise.

  19. FunctionDJ commented on Dec 11, 2025

    @FunctionDJ

    Is something like this currently possible?

    ---@generic T: string[]
    ---@param p1 `T`[]
    ---@return { [T]: string }
    function foo(p1) end
    
    local result = foo({ "bar", "quux" })
    print(foo.bar)
    --         ^ type is string
    print(foo.quux)
    --         ^ type is string
  20. ian-pascoe commented on Jan 12, 2026

    @ian-pascoe
    Contributor

    I have a PR open to address several of these. Mainly the high-priority ones

  21. chismar commented on Feb 16, 2026

    @chismar

    hi!

    If I have a generic function returning a table, and I pass it a table with some instance fields (see kingdom example further down) then autocomplete will find the fields of the kingdom table just fine

    so something like root(kingdom). will output gold, prestige, glory, etc correctly
    so generic T will basically treat my table and its fields as a full type - good

    ---@generic T
    ---@param t T
    ---@return T
    function root(t) return t end
    

    but If I return generic function which accepts a table of the type T (see example of usage with another_kingdom) - it will not autocomplete nor find the gold/glory/prestige of the table, so it doesn't understand that this table is of type T the same way as simple return does

    ---@generic T
    ---@param t T
    ---@return fun(t:T): T
    function instance_of(t)
        return getmetatable(t).__call;
    end
    
    kingdom = entity {
        gold = 10,
        prestige = 10,
        glory = 100
    
    }
    
    
    another_kingdom = instance_of(kingdom) {
    >> if I try to autocomplete here plugin will not suggest gold, glory, prestige, it treats the table as opaque <<
        gold = 100,
        glory = 50,
       prestige = 1
    }
    

    however if kingdom was of some --@type then it would suggest this type fields
    but not the instance fields, which work with simple returns

    would you be so kind as to add codepath similar to simple return for such cases?
    it would greatly improve experience with my entity definition tables

  22. Curve commented on May 2, 2026

    @Curve

    Is it possible to encode something like the following?

    ---@generic T
    ---@param obj T
    ---@param key string
    ---@return T[key]
    function table.get(obj, key) end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feat/genericRelated to generic emulation feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions