LuaLS / lua-language-server

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

Enhancement suggestion: mark function as class constructor #449

Closed winterwolf closed 2 years ago

winterwolf commented 3 years ago

Please look at this short example:

---@class Test
---@field 1 number
---@field 2 string
local Test = {}

---@param arg1 number
---@param arg2 string
function Test:new(arg1, arg2)
  assert(type(arg1) == 'number')
  assert(type(arg2) == 'string')
  return {arg1, arg2}
end

setmetatable(Test, {__call = Test.new})

local t = Test(1, 'done')

print(t[2])

It is the simplest emulation of class Test with the constructor new.

The problem here is that we don't get any suggestion after we type: Test( because your extension don't parse metatables.

I want to ask - is it possible to add some tag, like ---@constructor to be able to mark function new as the constructor for class Test? Thank you.

sewbacca commented 3 years ago

I don't think that ---@constructor is standard EmmyLua. I would suggest to default the metatable to the class itself and resolve metamethods like __call. There would be also another usecase:

local a = vector.new(1, 1)
local b = vector.new(2, 2)
local c = a + b

Currently c won't be resolved to a vector, but any, which is quite annoying sometimes.

sumneko commented 3 years ago

I will try to analyze meta-methods first. However, the concern is that there may be performance issues. I will decide the next step based on the test results.

Lua's meta-method syntax is too general, and it is difficult to make specific optimizations.

sewbacca commented 3 years ago

Maybe another solution would be to give information about metamethods in @class definition. However i don't know if EmmyLua supports that.

sumneko commented 3 years ago

However i don't know if EmmyLua supports that.

I have added many new annotations, this is not a problem.

sewbacca commented 3 years ago

Ah, I thought you didn't want to extend EmmyLua. Is there a list of annotations, that you have added so far (beyond what EmmyLua supports)?

sumneko commented 3 years ago

i will create a wiki about it

winterwolf commented 3 years ago

Of course, it would be perfect if the extension parse metatables, but I think adding a new tag is the easiest and safest solution. Parsing metatables can not only reduce performance, but also cause a lot of new bugs.

Ketho commented 3 years ago

i will create a wiki about it

Should I try to help document the basic things on EmmyLua annotations? Even though I don't know the implementation and don't even understand the tests on https://github.com/sumneko/lua-language-server/blob/master/test/definition/luadoc.lua

sumneko commented 3 years ago

i will create a wiki about it

Should I try to help document the basic things on EmmyLua annotations? Even though I don't know the implementation and don't even understand the tests on https://github.com/sumneko/lua-language-server/blob/master/test/definition/luadoc.lua

Sure, thank you!

serg3295 commented 3 years ago

@Ketho Hello. I would like to supplement your article 'EmmyLua Annotations' in the wiki. Just a few extra examples and a descriptions. Since you are the author of this article, I would not like to make changes to the article without your permission. Perhaps it makes sense to create a thread in Discussions where we could discuss the changes in the article beforehand.

Ketho commented 3 years ago

Yeah sure, make a thread in Discussions and we can talk 👍

PhoenixZeng commented 2 years ago

Lua's meta-method syntax is too general, and it is difficult to make specific optimizations.

确实 所以我建议只分析文档注释和对原方法的调用 而不是内部实现 开销应该小得多 Indeed, so I suggest that only analyzing emmydoc and calls to the metamethod rather than internal implementation should be much less expensive

@alias type.method type.__call

eigenbom commented 2 years ago

I'd like this feature also. In the meantime I found a workaround is to decorate Test as a fun, like so:

---@class Test
---@field [1] number
---@field [2] string
---@type fun(arg1:number, arg2:string):Test
local Test = {}
sewbacca commented 2 years ago

I had an idea for how to handle deferred constructor arguments. Consider this base Object class:

---@class Object
local Object = {
        __name = "Object"
}

--- Creates a new object of type T
---@generic T
---@param self T
---@return T
function Object:new(...)
        local object = setmetatable({}, self)
        object:init(...)
        return object
end

function Object:init(...) end

return Object

Now, when inheriting from a class, the new functions gives, as expected the new object.

---@class SubClass : Object
local SubClass = {}

function SubClass:init(value)
    self.value = value
end

-- No argument suggestions here :*(, but the type is correct :)
local object = SubClass:new(10)

However, it would be awesome, to also have argument completion. An option would be to have generic varargs, which infer the arguments of a given function. One would edit the base class as follows:

--- Creates a new object of type T
---@generic T
---@param self T
---@param ... T.init(self, ...) # Notice the pattern matching here. Alternativly I would suggest T:init(...)
---@return T
function Object:new(...)
        local object = setmetatable({}, self)
        object:init(...)
        return object
end

This would allow for argument completion in various scenarios, where the final varargs are determined by the given input. Another example (this hasn't been tested, but I already wrote similar code for Löve2D) would be:

local delayedCalls = { }

--- Registers a draw call which will be called, once the graphics module is enabled
---@generic T : function
---@param func T
---@param ... T(...)
local function register(func, ...)
    delayedCalls = { func = func, args = table.pack(...) }
end

local function draw(x, y, width, height)
    love.graphics.setColor(1, 1, 1)
    love.graphics.rectangle("fill", x, y, width, height)
end

-- No argument suggestions here :*(
register(draw, 10, 10, 100, 100)

function love.draw()
    for _, call in ipairs(delayedCalls) do
        call.func(unpack(call.args, 1, call.args.n))
    end
end

Currently register won't display the arguments used by func. (And I don't know if ---@generic T : function is supported), but this way I would think, the language server should have enough information to determine what arguments to expect.

Is this in the realm of the possibles?

lunarwtr commented 2 years ago

Perhaps could pull the solution from here in?

https://github.com/Benjamin-Dobell/IntelliJ-Luanalysis#callable-types

sumneko commented 2 years ago

Have supported ---@overload

PhoenixZeng commented 2 years ago

any eg about @overload? wiki have a eg about fun overload. how it work with construct? @sumneko

sumneko commented 2 years ago

any eg about @overload? wiki have a eg about fun overload. how it work with construct? @sumneko

---@class A
---@overload fun():A
local mt = {}