Skip to content

Overloaded methods do not properly return a generic bound to the self parameter #3087

Closed
@GrandpaScout

Description

@GrandpaScout

How are you using the lua-language-server?

Visual Studio Code Extension (sumneko.lua)

Which OS are you using?

Windows

What is the issue affecting?

Type Checking

Expected Behaviour

Given the following code:

---@class A
local A = {}

---@generic S
---@param self S
---@param str string
---@return S
function A:method1(str) end

---@generic S
---@param self S
---@param num1 number
---@param num2? number
---@param num3? number
---@return S
function A:method1(num1, num2, num3) end

---@class B: A
local B = {}

local x = B:method1("str")
local y = B:method1(1)
local z = B:method1(1, 2, 3)

The following should be true:

local x = B:method1("str")   --> local x: B
local y = B:method1(1)       --> local y: B
local z = B:method1(1, 2, 3) --> local z: B

All three local variables should be the B type as that is what generic S would be during this method call.

Actual Behaviour

The following is given instead by LuaLS:

local x = B:method1("str")   --> local x: unknown
local y = B:method1(1)       --> local y: unknown
local z = B:method1(1, 2, 3) --> local z: B

From my testing, it seems that given any amount of overloads and a method call the following seems to happen. Whether this is actually what happens in LuaLS is a different story as I could not figure out why this happens.

  • Change the method call's parameters to be all anys
    B:method1("str") --> B:method1(any)
  • Check if that new call matches more than one overload.
    B:method1(any) matches A:method1(str: string)
    B:method1(any) matches A:method1(num1: number, num2?: number, num3?: number)
  • If multiple overloads were matched, return unknown instead.

Reproduction steps

  1. Paste the following code into a lua file and open it:
    (Alternatively, use the test cases file found in the "Additional Notes" section)
    ---@class A
    local A = {}
    
    ---@generic S
    ---@param self S
    ---@param str string
    ---@return S
    function A:method1(str) end
    
    ---@generic S
    ---@param self S
    ---@param num1 number
    ---@param num2? number
    ---@param num3? number
    ---@return S
    function A:method1(num1, num2, num3) end
    
    ---@class B: A
    local B = {}
    
    local x = B:method1("str")
    local y = B:method1(1)
    local z = B:method1(1, 2, 3)
  2. Hover over locals x, y, and z.
  3. Notice that locals x and y have the wrong type while local z is fine.

Additional Notes

This method of returning self is being done because the self keyword does not actually resolve to the class being used for the method call and always seems to be class that created the method instead.

This error started appearing in 3.10.1 and has not been fixed yet as of 3.13.6.
Image
(The testing was done before 3.13.6 was released and I have personally tested 3.13.6 and confirm that it is still the same as 3.13.5)

The following file was used to get the information for the above chart.
testcases.txt

Reverting the changes made by #2898 and #2765 appears to fix this issue (while also re-introducing the bugs those PRs fixed.)

I have searched for other issues related to this, but only found mostly unrelated issues. However, i did find one comment on issue #723 that mentions this exact bug and the PR following it (#2898) seemed to only fix half of the issue. (As shown by version 3.12.0 correcting more than half of the test cases in the test case file.)

Log File

file_d%3A__test.log

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