Skip to content

fix: return single definition for anonymous-function field assignment - #3450

Open
tomlau10 wants to merge 1 commit into
LuaLS:masterfrom
tomlau10:fix/goto-definition-single-candidate
Open

fix: return single definition for anonymous-function field assignment#3450
tomlau10 wants to merge 1 commit into
LuaLS:masterfrom
tomlau10:fix/goto-definition-single-candidate

Conversation

@tomlau10

Copy link
Copy Markdown
Contributor

Fixes #2451

Summary

Go to definition on a field assigned an anonymous function returned two candidates on the same line — the field name and the function value.

A = {}
A.c = function() end
A.c()   -- goto definition on `c` previously returned 2 candidates: `c` and `function`

local f = function() end behaves the same way. After this PR, only the field name is returned, consistent with how literal values already behave (e.g. X.y = 1 jumps to y).

Root cause (click to expand)

vm.getDefs produces two independent defs through two paths:

  1. Name candidate (c): compileByNodeChain resolves the setfield LHS, and the definition provider unwraps it via src.field.

  2. Function-value candidate (function): searchByNodecompileNode merges the field's value into the compiled type. The function node survives the literal filter because function literals are explicitly exempted there — an exemption meant for generic type inference (e.g. v2 = f(function() end)), not for direct assignments.

Neither existing dedup catches it:

  • node-identity dedup in getDefs treats them as different nodes;

  • the nested-range dedup in sortResults only removes a target whose range contains another — these two are same-line siblings.

Fix (click to expand)

In script/core/definition.lua, after collecting targets, drop a function-value target when its owning assignment's name node is also a target:

local targetMark = {}
for _, res in ipairs(results) do
    targetMark[res.target] = true
end
for i = #results, 1, -1 do
    local target = results[i].target
    if target.type == 'function' then
        local parent = target.parent
        if parent and guide.isAssign(parent) then
            local owner = parent.field or parent.method or parent.index or parent.variable or parent
            if targetMark[owner] then
                table.remove(results, i)
            end
        end
    end
end

Why this shape:

  • Conditioned on the owner also being a target, so generic-inference defs (where the function literal is the only candidate) are left untouched.

  • Scoped to the definition provider only. type-definition and implementation already return a single candidate through their own filters (a type whitelist and getRefs + isAssign respectively), so they are unaffected.

Tests

  • Updated test/definition/function.lua: local f = function() end now expects a single definition at f.

  • Added test/definition/field.lua regression cases for both global and local tables (A.c = function() end).

  • Full test suite passes.


中文摘要

A.c = function() end 的寫法,goto definition 原本會回傳兩個結果(欄位名稱 c 和 function 值)。此 PR 在 definition.lua 加了一段處理:當欄位名稱也在結果中時,移除冗餘的 function 值,只保留名稱節點,與 X.y = 1 跳到 y 的慣例一致。type-definition / implementation 兩個 provider 因各自已有 filter,不受影響;測試全部通過。

此 bug 的根源,是 function 字面量豁免在 2022 年某次 global-manager/infer 大型 refactor (0e159ee037) 中順手加入的,屬 refactor 的副作用、並非刻意設計;回傳 2 個 candidate 的測試也是同期的批量 update commit (9451329b33) 帶入的。

本 PR 由 Claude Code(deepseek-v4-flash)協助分析與撰寫。

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice: It looks like you're using hustcer/deepseek-review, but the CHAT_TOKEN hasn't been configured in your repo's Variables/Secrets. Please ensure this token is set for proper functionality. For step-by-step guidance, refer to the CHAT_TOKEN Config section of README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two definitions for local function variables

1 participant