15

You can see a demo in this playground.

I've made a simple generic type which can represent either a variable or a function that returns a variable. But, unfortunately, it doesn't work with a typical typeof arg === 'function' check. It produces the following error:

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

Is there a way to make it work without using type guard function?

type Initializer<T> = T | (() => T)

function correct(arg: Initializer<string>) {
    return typeof arg === 'function' ? arg() : arg
}

function wrong<T>(arg: Initializer<T>) {
    return typeof arg === 'function' ? arg() : arg // error here
}

const isFunction = (arg: any): arg is Function => typeof arg === 'function'

function correct_2<T>(arg: Initializer<T>) {
    return isFunction(arg) ? arg() : arg
}

3 Answers 3

11

You can write:

type Initializer<T> = T extends any ? (T | (() => T)) : never

function correct<T>(arg: Initializer<T>): T {
    return typeof arg === 'function' ? arg() : arg // works
    // arg is Initializer<T> & Function in the true branch
}

const r1 = correct(2) // const r1: 2
const r2 = correct(() => 2) // const r2: number

In the original version, arg is resolved to (() => T) | (T & Function) in the true branch. TS apparently can't recognize for this union function type, that both constituents are callable. At least in above version, it is clear for the compiler that you can invoke arg after a function check.

Might also be worth to create a github issue for this case in the TypeScript repository - in my opinion T & Function should represent some (wide) type of function.

Sign up to request clarification or add additional context in comments.

3 Comments

That's a smart trick, thanks. I've created an issue here.
Heh, that GitHub issue says that this trick is the bug, and that the original behavior is correct (that part makes sense since if T is itself a function type like (x: string) => number, then it is not safe to call arg().
The issue also says working with instanceof Function is a good workaround
8

Using instanceof Function to check if arg is callable works nicely:

type Initializer<T> = T | (() => T)

function fixed<T>(arg: Initializer<T>) {
  // Instead of using `typeof`:
  // return typeof arg === 'function' ? arg() : arg // error here

  // use `instanceof`
  return arg instanceof Function ? arg() : arg
}

This was originally described by kentcdodds on a GitHub issue related to this.

Comments

4

I tried a different approach than that in accepted answer, by forbidding T (expected resolved value) to be a function. It seems to work in most use cases, unless you are trying to produce functions from the initializer.

type Initializer<T> = T extends Function ? never : T | (() => T);

function foo<T>(r: Initializer<T>): T {
  return typeof r === 'function' ? r() : r;
}


const valOK = foo('2');
const funOK = foo(() => 4);

const funError = foo((a: number, b: number) => a + b);  // Expected error

Playground link

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.