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

Unioned Function Types don't properly work as expected

Open Exponential-Workload opened this issue 1 year ago • 5 comments

How are you using the lua-language-server?

Visual Studio Code Extension (sumneko.lua)

Which OS are you using?

Linux

What is the issue affecting?

Annotations, Type Checking, Completion

Expected Behaviour

When given a type like

---@type (fun(dataModel: DataModel, service: '\'AdService\''): AdService)|(fun(dataModel: DataModel, service: '\'Players\''): Players)
DataModel.b = function(dataModel,service)--[[some logic]]end;

followed by

local obj = DataModel:b('AdService')

the type of obj should always be AdService - if 'AdService' is changed to 'Players', it should be of type Players, similar to strongly-typed languages like TypeScript and Luau.

Actual Behaviour

In the above example, obj is always of type AdService | Players.

Reproduction steps

See Above

Minimal Code Snippet:

--- @type (fun(type: '\'string\''): string) | (fun(type:'\'number\''):number)
local exampleFunc = function(dataType)return dataType == 'number' and 1 or 'hi';end;
local num = exampleFunc('number') --> desired type: number - received type: string | number
local str = exampleFunc('string') --> desired type: string - received type: string | number

image image

Additional Notes

No response

Log File

No response

Exponential-Workload avatar May 09 '23 00:05 Exponential-Workload

Bear in mind that in LuaCATS annotations (the ones used by this language server), the | character means union of types, not boolean OR operator.

C3pa avatar May 10 '23 10:05 C3pa

Bear in mind that in LuaCATS annotations (the ones used by this language server), the | character means union of types, not boolean OR operator.

On the other hand I'm not sure if the union of two functions is a function that can take any of their parameters and return any of their results. I haven't looked into the theory behind it, but in practice it seems to weaken the type system. For example:

---@type (fun(a : integer) : string) | (fun(a : string) : integer)
local f

local r_str = f(1)
local r_int = f('hi')

math.random(r_int) -- cannot assign string|integer to integer

A more exclusive definition of function union seems like a more useful construct, if the language server can support it.

From what little I know about TypeScript, it seems that LuaCATS | character works more like an intersection (&) operator rather than a union (especially coupled with the lack of support for exact/fixed types)

firas-assaad avatar May 10 '23 12:05 firas-assaad

I tried my example in TypeScript and it actually works differently. If I use | (union), the parameters are actually intersected and it's not possible to call the function with any arguments:

type f1 = (fruit: string) => number; 
type f2 = (color: number) => string;
declare let f: f1 | f2;


let r_str = f(1) // argument of type number is not assignable to never
let r_int = f('hi') // argument of type string is not assignable to never

Using & works as expected, however:

type f1 = (fruit: string) => number;
type f2 = (color: number) => string;
declare let f: f1 & f2;


let r_str = f(1) // r_str is string
let r_int = f('hi') // r_int is number

I also found this release notes page talking about the behavior of function unions.

In any case, TypeScript's behavior with both | and & is more consistent and useful than LuaCATS in my opinion. A union of functions should either restrict the parameters to their common intersection, or treat each function as a separate type (what this issue asks for).

firas-assaad avatar May 10 '23 12:05 firas-assaad

Bear in mind that in LuaCATS annotations (the ones used by this language server), the | character means union of types, not boolean OR operator.

On the other hand I'm not sure if the union of two functions is a function that can take any of their parameters and return any of their results. I haven't looked into the theory behind it, but in practice it seems to weaken the type system. For example:

---@type (fun(a : integer) : string) | (fun(a : string) : integer)
local f

local r_str = f(1)
local r_int = f('hi')

math.random(r_int) -- cannot assign string|integer to integer

A more exclusive definition of function union seems like a more useful construct, if the language server can support it.

From what little I know about TypeScript, it seems that LuaCATS | character works more like an intersection (&) operator rather than a union (especially coupled with the lack of support for exact/fixed types)

I believe that there is a prerequisite before implementing what you are describing. The language server can't determine the correct overload based on argument types. See #2084 for a brief overview of the issue, and #1456 covers it in detail.

C3pa avatar May 11 '23 06:05 C3pa

I don't know if this is the correct place, but the issue https://github.com/LuaLS/lua-language-server/issues/2214 (which has been closed), only started surfacing after version 3.6.24; https://github.com/LuaLS/lua-language-server/compare/3.6.23...3.6.24

Version 3.6.23 doesn't output these warnings

cwrau avatar Feb 26 '24 10:02 cwrau

This appears to have been fixed.

carsakiller avatar Aug 21 '24 20:08 carsakiller