lua-language-server icon indicating copy to clipboard operation
lua-language-server copied to clipboard

[Feature Request] Interfaces and Implementors

Open Perodactyl opened this issue 8 months ago • 1 comments

The current class system is alright, but imagine this: I have a Stream class which is extended by ReadStream, WriteStream, SeekableStream, etc.

---@class Stream
---@field close fun() Deallocates or hangs up a stream.

---@class SeekableStream: Stream
---@field seek fun(whence: SeekWhence, offset: integer): integer

---@class ReadStream: Stream
---@field read fun(self: ReadStream, length: integer): string|nil

---@class WriteStream: Stream
---@field write fun(data: string): integer

---@class StringStream: ReadStream, SeekableStream Streams data out of a string.

Now I have a function which takes any Seekable, Readable string. How do I specify this? Either we need a conjunction type (ReadStream & SeekableStream, which has keys from both), or we need (preferably) interfaces, which are generic and can be extended by classes. The reason I suggest interfaces is because if I write this:

---@param length integer
---@return string|nil
function StringStream:read(length)
	if self.buffer == nil then
		error("Stream is closed", 2)
	end
	if self.cursor > #self.buffer then
		return nil
	end
	local out = string.sub(self.buffer, self.cursor, math.min(#self.buffer, self.cursor + length))
	self.cursor = self.cursor + #out
	return out
end

and I omit the type annotation, it assumes read is already defined because StringStream extends ReadStream, and it doesn't validate types (arguments and return type are any).

Perodactyl avatar Apr 04 '25 19:04 Perodactyl

This is already supported to a certain extend by author in v3.10.6

  • Infer the parameter types of a same-named function in the subclass based on the parameter types in the superclass function. https://github.com/LuaLS/lua-language-server/blob/802e13ccac1153c32b90e6f203fa482fff7a603e/changelog.md?plain=1#L156-L160
  • at commit: https://github.com/LuaLS/lua-language-server/commit/7c481f57c407f7e4d0b359a3cfbce66add99ec2f

example

---@class ReadStream
local ReadStream

---@param length integer
---@return string|nil
function ReadStream:read(length)    --> length: integer
end

---@class StringStream: ReadStream

---@class StringStream
local StringStream

function StringStream:read(length)  --> length: integer
end

StringStream:read("")   --> Cannot assign `string` to parameter `integer`.

but may be bugged when using @field syntax ...

From your reproduction code snippet, I believe there maybe some bugs when defining the interface using @field syntax 😕 Because when you use @field to define the read() method, no type inference on the implemented method param.

---@class ReadStream
---@field read fun(self: ReadStream, length: integer): string|nil
local ReadStream

---@class StringStream: ReadStream

---@class StringStream
local StringStream

function StringStream:read(length)  --> length: any
end

StringStream:read("")   --> no type checking

current workaround

So the current workaround is to define the interface using function style (maybe you already have a separate @meta file?), instead of @field, before this issue is fixed.

tomlau10 avatar Apr 05 '25 01:04 tomlau10