Skip to content

Lua: function expressions assigned to table fields/keys produce no node — their calls are attributed to the file, so callers/impact silently omit them #1616

Closed

Description

@K1nG11

What happened

In Lua, a function expression assigned to a table field or a table-constructor key produces no node. Only function M.name() / function M:name() / local function name() are extracted.

The parser clearly walks into those function bodies — locals declared inside them do get variable nodes — but the enclosing function itself is never registered. Its outgoing calls are then attributed to the file node, so callers and impact report a confident-looking answer that silently omits real call sites.

Minimal reproduction

Two files, nothing else. codegraph 1.5.0, Windows.

util.lua

util = {}

function util.helper()
  return 1
end

return util

handlers.lua

local M = {}

function M.namedFn()          -- extracted
  return util.helper()
end

M.assignedFn = function()     -- NOT extracted
  return util.helper()
end

M.callbacks = {
  onStart = function()        -- NOT extracted
    return util.helper()
  end,
}

return M
codegraph init .     # 5 nodes, 6 edges

Actual

Nodes: file handlers.lua, variable M, method namedFn, file util.lua, method helper.
assignedFn and onStart are absent.

$ codegraph callers helper

Callers of "helper" (2):

method      namedFn
  handlers.lua:5

file        handlers.lua
  handlers.lua:1

Three call sites exist; two of them collapse into a single file handlers.lua:1 entry that names neither function and points at line 1. There is no uncertainty marker, so the answer reads as complete.

Expected

assignedFn and onStart extracted as function/method nodes named after the assignment target (M.assignedFn, M.callbacks.onStart), with their own outgoing calls edges — the same treatment function M.namedFn() already gets.

Why it matters

This is the dominant way real Lua codebases attach behaviour. Measured on an indexed 2,107-file Lua game client (25,623 nodes):

  • Every UI handler is invisible. Widgets bind callbacks as tbWnd.tbOnClick = { btnUse = function(self) ... end }; toolwide, togShowInHud, HOTKEY_QINGKUNG, fnCheck, fnMoveOnAttack → 0 nodes.
  • Dispatch tables are invisible — e.g. a tbKeyDownFun = { HOTKEY_RIDE = function() ... end, ... } hotkey table yields one variable node and nothing for the handlers.
  • Node kinds across the whole index: method 19,600 · variable 3,894 · file 2,107 · function 20 · import 2. The function count is 20 because only local function reaches that kind.

The impact is not just missing nodes: every call made from inside these functions is re-attributed to the file, which both hides callers and inflates the file node's apparent fan-out.

Related

Variant worth folding in

local f = function() ... end does produce a node, but with kind = variable, and no calls edge ever targets it — so calls through f() are lost the same way.

Activity

  1. colbymchenry commented on Sep 8, 2026

    @colbymchenry
    Owner

    Fixed by #1778 / #1650

    This is the same extraction gap as #1650. The landed fix in #1778 (38cb0bd5, merge 8c047342 on main) already covers the cases reported here:

    • M.assignedFn = function() … end → indexed as a method
    • keyed table fields like M.callbacks = { onStart = function() … end } (including nested tables such as tbWnd.tbOnClick = { btnUse = … })
    • local f = function() … end → indexed as kind:function (not a bare variable)
    • calls from those bodies are attributed to the named symbols, not collapsed onto the file node

    CHANGELOG Unreleased already cites both #1616 and #1650.

    Linux verify (current main @ b8d46d13, Node v22.19.0)

    Minimal repro from this issue (util.lua + handlers.lua) plus the local f variant:

    Indexed 3 files → 9 nodes, 11 edges
      method×4 (namedFn, assignedFn, onStart, helper)
      function×1 (f)
      file×3, variable×1
    
    $ codegraph callers helper
    Callers of "helper" (4):
      method      namedFn      handlers.lua:3
      method      assignedFn   handlers.lua:7
      method      onStart      handlers.lua:12
      function    f            local_fn.lua:2
    

    No file handlers.lua collapse. Pre-fix this issue reported only namedFn + a file-level attribution for the missing sites.

    Nested / static-bracket residual probe also passes (btnUse, nested onClick, M["bracketed"]); dynamic bracket keys remain intentionally unguessed (same policy as #1778).

    No follow-up PR needed — closing as completed by #1778.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions