Repository navigation
Function Overloading Overhaul (@function annotation) #1456
Description
The Problem
I think function overloading needs some changes in order for it to really function in the way that most people would find useful. This is especially problematic with event systems, as I and others have encountered.
Example
Currently, let's say I have the following function that I want to provide an overload for:
function Shape:listen(event, callback) endUsing @overload
The first logical option is to use @overload:
---@class Shape
local Shape = {}
---Subscribe to an event on this shape
---@param event "Destroy"
---@param callback fun(self: Shape)
---@overload fun(event: "Repair", callback: fun(self: Shape, amount: number))
function Shape:listen(event, callback) endBut there is a problem, when using methods (:), the first parameter only gets completions for the first @param and ignores the @overload entirely.

Ok, so for testing, let's replace the method (:) with a static function (.):
---@class Shape
local Shape = {}
---Subscribe to an event on this shape
---@param event "Destroy"
---@param callback fun(self: Shape)
---@overload fun(event: "Repair", callback: fun(self: Shape, amount: number))
function Shape.listen(event, callback) endThis still isn't great, we are still offered both callbacks even though the info we have entered only matches the @overload. At least the first parameter was completed this time.

Multiple Definitions
So then maybe we try defining multiple functions where each event param has the type set to the event name we are looking for:
---@class Shape
local Shape = {}
---Subscribe to an event on this shape
---@param event "Destroy"
---@param callback fun(self: Shape)
function Shape:listen(event, callback) end
---Subscribe to an event on this shape
---@param event "Repair"
---@param callback fun(self: Shape, amount: number)
function Shape:listen(event, callback) endNow, even as methods (:) we are receiving correct completions for the first parameter... nice! However, we are still receiving two completions for the callback - there is no narrowing happening. The completion also shows the event as "Destroy", which is incorrect for our second definition as we have only allowed "Repair".

At least when defining the function twice, we are able to write a separate description for each of them as well as their @params and @returns. However, we receive a warning saying that we have a duplicate field.
Proposed Solution
See @flrgh's idea to add a @function annotation to add more in-depth support for defining functions overall.
Activity
Derpius commentedon Aug 9, 2022
This would also fix another issue I'm having where classes that have both a static and method variant of the same function aren't narrowing, eg:
carsakiller commentedon Aug 9, 2022
Yeah, I don't know of a way where you can change the callback param given a certain first parameter, which is a very common use-case for an event system or asynchronous code. Hopefully changing how overloads work and improving the narrowing makes it possible 🙂
serg3295 commentedon Aug 10, 2022
This problem has been partially solved. However, the inconvenient thing is that it is impossible to display comments for each event separately.
or
flrgh commentedon Aug 10, 2022
Poking my head in to add ideas here (read: personal wish list features 😉). I think a good solution/step would be to support functions as first-class objects alongside classes.
The issue with function aliases (
fun(...))The
fun(...):...syntax is so terse/dense that it becomes cumbersome to use for all but the most simple cases. Also, if you're properly namespacing your custom types in order to not pollute the global ns, you wind up with reeeeeally long lines:An alternative: standalone function definitions
This would make things a lot more flexible.
The @function label
Example:
Reusing @Class
Alternatively, the
@classlabel/decorator/keyword could be overloaded to support function annotations:Applying the overload
Now, to apply
libraryname.functionname-with-callbackas an overload ofdo_the_thing, we have options...Overload at the target by passing a type name to @overload
Annotate at the source with a new
@overloadslabelIf we allow
@functionto be applied to actual code, then we can take it a step further and allow overloading from different sources/files, which would be nice for anyone working with framework-y codelibraryname.luaother.luaThe benefit of
@functionand@overloadsis that now it's possible to create function annotations without having to render actual code, which would be a big win IMO.carsakiller commentedon Aug 10, 2022
I think adding a
@functionannotation would be a nice replacement for thefun(...):...syntax. I too dislike the current way of defining functions because of all the information that is missing and because they have to be one line.@overloadcould also be "replaced" with the functionality you explain with@overloadswhich refers to a@functionor normal Lua function. For this to really work though, we would probably need to namespace annotations, like @flrgh said. It would be nice if this could be handled automatically so that an annotation on a functionmyFunctioninmyLibrary.luacould be referred to by usingmyLibrary.myFunction.carsakiller commentedon Sep 13, 2022
Here is another issue I am experiencing with
@overload, the returns are not narrowing and it results in a union type... no matter how I try to define it:resultisboolean|stringin all of these casesTurtleP commentedon Jan 24, 2023
One other thing with this is that if you're trying to overload something that already exists, it will give you both definitions, which is not ideal (observed by @carsakiller and myself).
On top of that, if you try to
nilout an existing function (which perhaps isn't really supported because why would you do it?) doesn't do anything. I'd like to be able to remove an existing documented function that's in the LÖVE documents, but doesn't exist for LÖVE Potion.See #1834 for the discussion about this.
hinell commentedon Nov 23, 2023
@functiontag which would define a new instance function type that can be referenced by@type@overloadper doc isn't intended to be used for function declaration, it's OOP concept for already declared funcs[-]Function Overloading Overhaul[/-][+]Function Overloading Overhaul (`@function` annotation)[/+]bavalpey commentedon Jun 9, 2024
I'm wondering if there is any status on this or plans for the
@functiontag?vaderkos commentedon Aug 20, 2024
Looks like same issue

tomlau10 commentedon Aug 21, 2024
reply to: #1456 (comment)
It's unrelated to
@overload, it's because in thefun()syntax, there is an ambiguity between function has multi return and function has multiple arguments.fun(valu, key, cnt): any=> this is a function that returns
anyfun(valu, key, cnt): any, tbl: table=> this is also a valid syntax that a function return
anyandtablefun()that you defined forconsumerather than a 2nd
tblargument forTable.EachitselfYou have to add
()to thefun()in your first@overloadto eliminate this ambiguity:This should work for your case 😄 @vaderkos
tomlau10 commentedon Aug 21, 2024
I want to add that, I tested some of the incorrect type narrowing issues mentioned in the thread and they actually work in latest version of v3.10.5 🎉 (maybe get fixed in the middle, or by the recent #2765 )
I tested the following and they work, so they maybe closed 😄
@functionannotation) #1456 (comment)carsakiller commentedon Aug 21, 2024
Wow, the original example does seem to work!
I still have trouble with CC:Tweaked's event system, though, as an example.
I don't want to define the function 30+ times with all the same description, so I can't just do what is in the original example(forgot descriptions are inherited from any overload with one). But overloads also don't work for me here. There is no way to handle thestring...variadic returns forcomputer_commandevents, and becauseos.pullEvent()can be used with custom events, it means I have to also accept any string and return potentiallyany...value.Any?
This results in all of my returns being
anyeven when they are explicitly defined as having a set value for that case.Unknown?
This results in everything being
unknownand I don't even get a warning for:sub-ing anunknown.Try redefining?
This is slightly better and slightly worse than the above attempt to use
@overloadwithunknown. The other problem here is this wouldn't be an option at all if I weren't writing@metadefinitions and were instead writing runtime code.I have tried a bunch of combinations of different tricks but am still unable to get
os.pullEventworking nicely with a proper description, completion, and type checking for all 30+ events plus custom events.... pull requests accepted 😆
tomlau10 commentedon Aug 22, 2024
Yeah, seems
<type>...is not supported infun()syntax when using it in@overload. This only works when you write it by redefining functions under a@metadefinition file (where you can use@return string ...there)LuaLS's official wiki also suggested to use
@metawith function definition instead of just using@overload😂https://luals.github.io/wiki/annotations/#overload
I think the issue you encountered here is that you have to support custom events, in which you have to add
stringto the@alias ccTweaked.os.event. This just messes up the luals type narrowing feature, because all your built-in event (alarm/char/ etc) can be casted tostring. So the base function signature that returnsany ...always matches and cannot be filtered out. 😕If you remove the base function signature, then everything works fine. (ofc I know this cannot be the solution, because you have to support custom events).
Some immature idea
When I was trying to fix #2758 (comment), I leant that the logic of finding matched call resides in here:
lua-language-server/script/vm/function.lua
Line 359 in ddc96bd
Currently it just uses
vm.canCastTypewhen checking if each param can match that in the overload. I have some immature idea that, maybe we can try to further narrow thefuncs[]:As
"alarm"can be casted tostring, it matches=> so the basic function signature is included there (which causes the problem)
vm.canCastType, but I don't know how currently)os.pullEvent("alarm"), in the 1st pass it matches2function signatures when using thevm.canCastType(): the overload one"alarm"and the basic onestring"alarm"1, while the basic one has length0As this is unrelated to
@functionwhich this issue suggested, I should probably open a new one to track it when my idea become more mature 😄 Stay tuned @carsakillertomlau10 commentedon Aug 22, 2024
reply to: #1456 (comment)
(this maybe a bit late 🙈 )
I think I know why your example code doesn't work as expected. It's because how LuaLS filter out unmatched function signatures. It first check the params count:
lua-language-server/script/vm/function.lua
Lines 392 to 408 in ddc96bd
In your code, you have
Foo.Subscribe(eventName, callback)andFoo:Subscribe(eventName, callback)2 params(min = 2, max = 2)Foo.Subscribe(self, eventName, callback)=> that's
3 params(min = 3, max = 3)So when you are typing the code in the middle
inst:Subscribe("it can be viewed as
inst.Subscribe(self, "2 paramsare defined (amin = 2, amax = 2)Foo:Subscribe(eventName, callback)overload get filtered outit requires 3 params, which is more than you supplied in this intermediate code
(the
if amin >= min and amax <= maxcheck failed)Foo.Subscribe(eventName, callback)left ...=> then LuaLS infers that your
""is thecallbackparam 🤦♂️If the
callbackparam is declared optional then LuaLS can type narrow it correctly, because in that casemin = 2, max = 3I don't know if this type narrow logic can be optimized, but I don't think an
@functionwill help this case either. What I can be certain is that it should be another issue to track it. 🤔 @Derpiuscarsakiller commentedon Aug 23, 2024
Someone really smart and good-looking must've written that, @tomlau10 😉
Bilal2453 commentedon Oct 26, 2024
After 2 days (probably more) of trying to find a solution, I think I got it! Here is what we have, the following is what people have already been doing to get type narrowing correct with the callback completion, as described by @serg3295:
Problem with this as described is you won't be able to have descriptions which is what the proposed
@functionis for in the context of this problem (it is useful for other things overall and should be added), a workaround for this is using aliases with enumerations---|, so like this:It is tedious when you have a lot of events like this, but not like I will be the on responsible for the large annotation packages in the market place 😝
Edit: never-mind perhaps! seems like I was too excited and didn't notice callback not being narrowed correctly anymore, how sad!
A different approach to this is to define a general enumeration alias that contains all events with their descriptions, but this will result in a duplicated suggest, like below:
Screencast.From.2024-10-27.03-04-43.mp4
tomlau10 commentedon Nov 1, 2024
reply to: #1456 (comment)
I tested the given code snippet, although the auto suggestion is not narrowed correctly at the beginning, it works after typing
fand retrigger auto completion 🤔I guess this is due to different logic of type narrow between completion and match call
on fun(self: self, event: events.success, cb: fun(client))requires3arguments:self, event, cbemitter:on("success",, there are only2arguments:self, eventlua-language-server/script/vm/compiler.lua
Lines 600 to 611 in 3894968
amin, amax(min/max argument) count:lua-language-server/script/vm/function.lua
Lines 430 to 446 in 3894968
Therefore when you changed to use
aliasinside thefun()annotation, the type narrow in completion fails to work 😕lua-language-server/script/vm/compiler.lua
Lines 962 to 977 in 3894968
Bilal2453 commentedon Nov 2, 2024
I see. What would be the suggested "fixes"?
I would imagine fixing the completion step is the preferred route here, by allowing alias types / alias strings along side string values. Maybe also make it so that the match all case account for the 1 argument difference?
Not really experienced around this code which appears complex to me😆
Reminder that the alias solution is a hack around the last proposed one, which ends up duplicated
eventsuggestions. This one feels easier to fix, by merging equal values as one suggestion instead of letting it duplicate, this merge should account for things like comments.tomlau10 commentedon Nov 2, 2024
Yes, I agree that this is the preferred route.
I am trying to come up with a fix for this use case, and after some trial and error I think I nailed it 👀 .
I will open a PR for it later after more testing. 😄 @Bilal2453
Here is my work in progress (subject to change): tomlau10@4de84f3
UnknownLITE commentedon Apr 21, 2026
Any updates on this issue?
UnknownLITE commentedon Apr 21, 2026
Ok, so I found the syntax mentioned in #2932 works for field/methods.
For static methods, we have to use this syntax:
This syntax also works for instance methods
type narrowing doesn't work if you use multiple function definitions to define overloads for some reason(#3396 (comment))