I came across this strange type error saying that I cannot call a value of type function. I am so confused about this error. It’s basically saying that functions cannot be called…?
How to reproduce it:
Use this code:
--!strict
type Disconnectable = {
Disconnect: (self: Disconnectable) -> (...any);
} | {
disconnect: (self: Disconnectable) -> (...any)
} | RBXScriptConnection
local x: Disconnectable = workspace.ChildAdded:Connect(function()
print("child added")
end)
if type(x.Disconnect) == "function" then
x:Disconnect()
end
Expected behavior
I expected no type error. At the very least, I expected x.Disconnect to have a (...any) -> (...any) type inside the if-statement, since it is not know what exactly this function accepts/returns.
I don’t know much about TypeScripting, but looking at your code I managed to get a successful way to write the “Disconnectable” type without getting a warning on the ‘if’ statement. I concluded that you want to include both the methods ‘Disconnect’ and ‘disconnect’, so I changed the >> | << separator and used: &. As showed:
type Disconnectable = {
Disconnect: (self: Disconnectable) -> (...any);
} & {
disconnect: (self: Disconnectable) -> (...any)
} & RBXScriptConnection
local x: Disconnectable = workspace.ChildAdded:Connect(function()
print("child added")
end)
if type(x.Disconnect) == "function" then
x:Disconnect()
end
Now, autocompletion suggests: x:Disconnect(), x:disconnect() and x.Connected.
Writing & instead of | in your code implies that a value with a Disconnectable type has all of those properties/functions. So I try to avoid that right now.
Hi there, thanks for the report! The underlying cause here is that the refinement from type(x.Disconnect) == "function" only lets us learn that Disconnect is expected to be a function, but that’s insufficient to disambiguate which variant of your union you actually have, and so it gives an error. The specific error you get can (and likely will) change in the future to be more clear, but the fact that this program errors is right — we cannot know if it’s a table with Disconnect or a RBXScriptConnection based on the refinement. You get a more clear error today if you write the type a little bit differently:
-- this is a little lossy as a type, we've gained a legal state where neither are present, but if your intended use case is testing for the property before calling it, this is more accurate
type Disconnectable = {
Disconnect: (self: Disconnectable) -> (...any)?,
disconnect: (self: Disconnectable) -> (...any)?,
} | RBXScriptConnection
local x: Disconnectable = workspace.ChildAdded:Connect(function()
print("child added")
end)
-- testing for the presence of the property, not that its runtime type is function
if x.Disconnect then
x:Disconnect() -- we cannot determine the appropriate result type here
end
This may change a bit in the future to allow a call on the union if (and only if) all of the possible return types are exactly the same, but that’s not the case at all with your current type annotations anyway. That change would be a feature request, rather than a bug report though.
In an upcoming release, we’ll also ship a rewording of the “cannot call” error in particular to clarify what’s really going on with that refinement (i.e. that even though functions are callable in the abstract sense, the type function does not provide enough detail in order for us to determine the result type).