Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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 this issue Jun 11, 2024 · 1 comment

Comments

@necrosisy
Copy link

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
Copy link
Contributor

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

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

No branches or pull requests

2 participants