Skip to content

Is it possible to define type hints on the fly based on a string in lua? #3369

Closed

Description

@ChillerDragon

I have the following lua script

Game:register_rcon("", "i[client_id]s[name]", "", function (client_id, args)
	print(args.client_id) -- integer
	print(args.name) -- string
end)

The string "i[client_id]s[name]" passed to register_rcon determines the shape and types of the callbacks args argument. Right now I typed the callback quite generically

---@param callback fun(client_id: integer, args: table<string, string|integer|nil>) the callback that will be run if a user typed the command name into the remote console
function Game:register_rcon(name, parameters, helptext, callback) end

Can I instead of saying table<string,string|integer|nil> generate a proper table with field names and their types somehow?

Activity

  1. tomlau10 commented on Mar 9, 2026

    @tomlau10
    Contributor

    I believe you will want the plugin feature of LuaLS. 🤔
    Basically it can dynamically transform the text buffer viewed by the server

    • by adding virtual annotation lines
    • => this alters the server's type infer based on the transformed text buffer

    Here is a discussion showing what can be achieved by a plugin: #3286 (comment)


    Before diving deep into the plugin

    Let's first focus on what virtual annotation is needed.

    In your case you want to dynamically parse the "i[client_id]s[name]"
    => and generate an annotation for the args param
    Say we want the transformed buffer to be something like this:

    Game:register_rcon("", "i[client_id]s[name]", "", function (client_id, args)
    	---@cast args { client_id: integer, name: string }
    	print(args.client_id) -- integer
    	print(args.name) -- string
    end)
    • Now knowing what we want to achieve
      I just throw this problem description to google ai mode and let it write the script 😂
      with a few round of conversations, ai gives me this, and it actually works 😲
    plugin.lua

    --- @param uri string The URI of the file
    --- @param text string The content of the file
    --- @return nil | table[] A list of differences to be applied
    function OnSetText(uri, text)
        local diffs = {}
    
        -- Pattern explanation:
        -- 1. Game:register_rcon(
        -- 2. Capture the format string (Group 1)
        -- 3. Move to the function definition
        -- 4. Capture the parameters inside "function(...)" (Group 2)
        -- 5. Get the cursor position at the end of the parameters (Group 3)
        local pattern = "Game:register_rcon%s*%(.-%s*,%s*\"(.-)\"%s*,.-,%s*function%s*%((.-)%)()"
    
        for rcon_format, params_list, func_body_start in text:gmatch(pattern) do
            -- Extract the 2nd parameter name from the callback function
            -- Matches: first_param, second_param
            local second_param = params_list:match("%s*[^,%s]+%s*,%s*([^,%s]+)")
    
            -- Only proceed if the 2nd parameter exists
            if second_param then
                local fields = {}
                local type_map = {
                    i = "integer",
                    s = "string",
                    f = "number",
                    b = "boolean"
                }
    
                -- Parse the format string, e.g., "i[client_id]s[name]"
                for type_char, field_name in rcon_format:gmatch("([isfb])%[(.-)%]") do
                    local lua_type = type_map[type_char] or "any"
                    table.insert(fields, string.format("%s: %s", field_name, lua_type))
                end
    
                if #fields > 0 then
                    -- Construct the @cast using the dynamic parameter name
                    local cast_line = "\n        ---@cast " .. second_param .. " { " .. table.concat(fields, ", ") .. " }"
    
                    table.insert(diffs, {
                        start  = func_body_start,
                        finish = func_body_start - 1,
                        text   = cast_line,
                    })
                end
            end
        end
    
        return #diffs > 0 and diffs or nil
    end

    Image

    but still some more problems

    I see that you mentioned another luals addon project.
    By asking google ai mode, it said an addon cannot auto include a runtime plugin when user install it (due to security reason I guess). 😕
    The only way seems need to ask users to include the addon plugin to their project .luarc.json themselves.

    Also the above plugin logic uses normal text:gmatch(pattern) to match a line.
    If user write the function using multiple lines then it wont work.
    Also it parses the 2nd param as string, if it is a variable then this approach won't work either.

    You may need to research further.

  2. ChillerDragon commented on Mar 9, 2026

    @ChillerDragon
    Author

    @tomlau10 woah! Thanks a lot for that ❤️ this is really helpful! It is a working prototype. I could get it to run on my machine. I will look into how many limitations I will run into with this and how to ship it. But if users just have to do one luarocks install command to get those hints that would be already close to perfection.

    I did not expect this to be possible at all. Good that I gave asking a shot. This is brilliant!!!!!!!!!!!!!!!! 🚀

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions