#Type 'T & Function' has no call signatures

33 messages · Page 1 of 1 (latest)

thorn girder
#

Surely been asked before but I can't find the right search terms atm...

Why does this:

function nextItem<T>(item: T | (() => T)) {
  const next = typeof item == "function" ? item() : item;
}

Give me this error:

This expression is not callable.
Not all constituents of type '(() => T) | (T & Function)' are callable.
Type 'T & Function' has no call signatures.(2349)

This is one of those things that I'm both sure TS has a good reason to work this way, but I have no idea why. How could it not be a callable function right of the type guard?

I also noticed this does narrow the type and avoid the type error:

function nextItem2<T>(item: T | (() => T)) {
  const next = "call" in item ? item() : item;
}

But that does not seem right at all.

#

Also noticed that if I make T extends object it works fine... but that's not actually right for my use case.

tacit canyon
#

How could it not be a callable function right of the type guard?
the type guard just checks if it's a Function, which indeed does not have call signatures
it has a runtime call signature, but not a typed call signature. ts doesn't know what it returns or takes.

T could be a function itself, or could even be Function directly
ts wouldn't be able to see the call signature there

thorn girder
#

Hm. I only half get that... I can see an issue that TS doesn't know what kind of function it is, but if I write a custom type guard like

const isFunction = (val: any): val is Function => typeof val == "function"

Then this works:

const next = isFunction(item) ? item() : item;

And next is even T.

#

It seems there's something a bit more arcane, the typeof guard is not narrowing down to Function but to T & Function and of course T is not necessarily callable. But why doesn't it narrow down to Function?

#

Is it because a function can actually be a whole ass object in JS? So maybe it is { shape of T } & Function?

tacit canyon
#

Function is not callable.

thorn girder
#

What do you mean by that?

declare const fn: Function;
fn(); // no error...
tacit canyon
#

T & Function is correct, it must be some subset of T due to the initial type and some subset of Function due to the narrowing, so it's a subset of the intersection of those

#

sec

#

Function is almost never used so i guess i'm just misremembering, mb

thorn girder
#

I guess that part makes sense... but then I'm not sure why T & Function is not callable, though I've known about that behavior for awhile. I'm not sure how I'm supposed to narrow down to a callable type then?

#

I mean my user defined typeguard does work...

#

But feels hacky since it's literally just wrapping a typeof guard that doesn't work inline

tacit canyon
#

there's a runtime issue with that anyways though

#

if T is a function then it gets called even if it's not the supplier function in the union

#

do you know that T is not a function to begin with?

thorn girder
#

In my use case, yes. I couldn't figure out how to constrain it like that, though.

tacit canyon
#

what can T be?

thorn girder
#

Anything but a function 🙂

#

Mainly objects and primitives though

tacit canyon
#

well, functions are objects

#

so that's an issue

#

ts doesn't have subtractive types

thorn girder
#

I thought they weren't part of the object type though

#

Which I figure is why T extends object worked

remote thicketBOT
#
type T = (() => void) extends object ? true : false
//   ^? - type T = true
tacit canyon
#

object is any nonprimitive

thorn girder
#

Now I'm even more confused... why the typeof item == "function" works if T extends object

tacit canyon
#

no clue

#

does T extends {} work

thorn girder
#

No. But { [k: string]: any } does. I'm out of my depth here lol