LuaLS / lua-language-server

A language server that offers Lua language support - programmed in Lua
https://luals.github.io
MIT License
3.29k stars 305 forks source link

When the subclass inherits the constructor of the parent class, generics do not work (the type of `self` cannot be used for annotation) #2706

Open necrosisy opened 3 months ago

necrosisy commented 3 months ago

How are you using the lua-language-server?

Other

Which OS are you using?

Windows

What is the issue affecting?

Annotations, Type Checking

Expected Behaviour


---@class Vehicle
Vehicle = {}

---comment
---@generic T
---@param self `T`
---@param o? T
---@return T
function Vehicle:new(o)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  return o
end

---@class Plane: Vehicle
Plane = {}

setmetatable(Plane, { __index__ = Vehicle })

local obj = Plane.new()

图片

图片

The code prompt given when calling a constructor annotated with generic parameters that is inherited by a subclass is incorrect.

The prompt message should be

 function Plane.new(self: Plane, o?: Plane)

And the type of the obj variable in the code hint should be Plane instead of unknown

Actual Behaviour

---@param o? self
---@return self
function Vehicle:new(o)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  return o
end

self should be used to refer to the type of the method caller. And the self method should replace the generic method to annotate the type in this use case

Such type annotations should work, and code completion should show self as the caller's type

Reproduction steps

  1. Copy example to editor
  2. Call code prompt
  3. The prompt result is not as expected

Additional Notes

No response

Log File

No response

tomlau10 commented 3 months ago

First of all

I think you misused the backtick capture annotation `T`, this is for capturing a literal string param and convert it into a class. See the example Capture with Backticks in the wiki: https://luals.github.io/wiki/annotations/#generic In your case you should just use T (without backtick) when capturing the type of self param.

Secondly

your Plane.new({}) call syntax seems to be wrong. Because you declare Vehicle:new() using a : syntax (that's why the 1st argument will be a self), I think you should write Plane:new({}) or Plane:new() instead. Otherwise the empty table {} you passed into it will become self as it's the 1st param when you call the Plane.new() function.

Thirdly

the metatable key is __index but not __index__ (maybe you just mistyped? 😄 ), otherwise the above code snippet will not work as Plane.new will be nil instead.


In short I think your code would be something like this:

---@class Vehicle
Vehicle = {}

---comment
---@generic T
---@param self T
---@param o? T
---@return T
function Vehicle:new(o)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  return o
end

---@class Plane: Vehicle
Plane = {}

setmetatable(Plane, { __index = Vehicle })

local obj = Plane:new({})  -- now `obj` will be recognized correctly as `Plane` type by LuaLs
print(getmetatable(obj) == Plane)       -- true
print(getmetatable(obj) == Vehicle)     -- false

local obj2 = Vehicle:new() -- `obj2` will be recognized as `Vehicle` type by LuaLs
print(getmetatable(obj2) == Plane)      -- false
print(getmetatable(obj2) == Vehicle)    -- true